1 R Setup and Required Packages

In this project, the open-source R programming language is used to model the progression in the COVID-19 pandemic in different U.S. counties. R is maintained by an international team of developers who make the language available at The Comprehensive R Archive Network. Readers interested in reusing our code and reproducing our results should have R installed locally on their machines. R can be installed on a number of different operating systems (see Windows, Mac, and Linux for the installation instructions for these systems). We also recommend using the RStudio interface for R. The reader can download RStudio for free by following the instructions at the link. For non-R users, we recommend the Hands-on Programming with R for a brief overview of the software’s functionality. Hereafter, we assume that the reader has an introductory understanding of the R programming language.

In the code chunk below, we load the packages used to support our analysis. Note that the code of this and any of the code chunks can be hidden by clicking on the ‘Hide’ button to facilitate the navigation. The reader can hide all code and/or download the Rmd file associated with this document by clicking on the Code button on the top right corner of this document. Our input and output files can also be accessed/ downloaded from fmegahed/covid19.

if(require(pacman)==FALSE) install.packages("pacman") # check to see if the pacman package is installed; if not install it
if(require(devtools)==FALSE) install.packages("devtools") # check to see if the devtools package is installed; if not install it

# to check and install if these packages are not found locally on machine
if(require(albersusa)==FALSE) devtools::install_github('hrbrmstr/albersusa') #install package if needed
if(require(albersusa)==FALSE) devtools::install_github('dreamRs/r2d3maps') #install package if needed


# check if packages are not installed; if yes, install missing packages
pacman::p_load(tidyverse, magrittr, janitor, dataPreparation, lubridate, skimr, # for data analysis
               COVID19, rvest, readxl, # for extracting relevant data
               DT, pander, stargazer, knitr, # for formatting and nicely printed outputs
               scales, RColorBrewer, DataExplorer, tiff, grid,# for plots
               plotly, albersusa, tigris, leaflet, tmap, # for maps
               zoo, fpp2, NbClust, # for TS analysis and clustering
               VIM, rgdal,  spdep,  nimble, fastDummies, matrixStats, # for spatial regression 
               nnet, caret, # for multinomial regression modeling
               conflicted) # for managing conflicts in functions with same names

# Handling conflicting function names from packages
conflict_prefer('combine', 'dplyr') # Preferring dplyr::combine over any other package
conflict_prefer('select', "dplyr") #Preferring dplyr::select over any other package
conflict_prefer("summarize", "dplyr") # similar to above but with dplyr::summarize
conflict_prefer("filter", "dplyr") # Preferring filter from dplyr
conflict_prefer("dist", "stats") # Preferring dist from stats
conflict_prefer("as.dist", "stats") # Preferring as.dist from stats

# Custom Functions
source_url('https://raw.githubusercontent.com/fmegahed/covid19-deaths/master/Markdown/custom_functions.R')

set.seed(2020) # to assist with reproducibility
sInfo = sessionInfo() # saving all the packages/functions and session info

2 Extracting the Datasets

For our analysis, we fuse data from multiple sources. We describe the process of obtaining and merging each of these sources in the subsections below.

2.1 Time Series Data

In this section, we utilize the COVID19 package to obtain the following information: (Guidotti & Ardia, 2020)

  • Confirmed cases, recoveries and deaths;
  • policy information (e.g., transport closing, school closing, closing event, movement restrictions, testing policies, and contact tracing); and
  • Population and standard geographic information for each county.

From this information, we have also computed the new daily and weekly confirmed cases/deaths per county. The data is stored in a tidy format, but can be expanded to a wide format using pivot_wider() from the tidyverse package.

endDate = '2021-01-02'
endDatePrintV = format(ymd(endDate), format = "%b %d, %Y")

counties = covid19(country = "US", 
                   level = 3, # for county
                   start = "2020-03-01", # First Sunday in March
                   end = endDate, # end Date 
                   raw = FALSE, # to ensure that all counties have the same grid of dates
                   amr = NULL, # we are not using the apple mobility data for our analysis
                   gmr = NULL, # we are not using the Google mobility data for our analysis
                   wb = NULL, # world bank data not helpful for county level analysis
                   verbose = FALSE)

counties %<>% # next line removes non-contiguous US states/territories
  filter(!administrative_area_level_2 %in% c('Alaska', 'Hawaii', 'Puerto Rico', 'Northern Mariana Islands', 'Virgin Islands')) %>% 
  fast_filter_variables(verbose = FALSE) %>% #dropping invariant columns or bijections
  filter(!is.na(key_numeric)) %>%  # these are not counties
  group_by(id) %>% # grouping the data by the id column to make computations correct
  arrange(id, date) %>% # to ensure correct calculations
  mutate(day = wday(date, label = TRUE) %>% factor(ordered = F), # day of week
         newCases = c(NA, diff(confirmed)), # computing new daily cases per county
         newDeaths = c(NA, diff(deaths)) )  # computing new daily deaths per county

# manually identifying factor variables
factorVars = c("school_closing", "workplace_closing", "cancel_events",
               "gatherings_restrictions", "transport_closing", "stay_home_restrictions",
               "internal_movement_restrictions", "international_movement_restrictions",
               "information_campaigns", "testing_policy", "contact_tracing")

counties %<>% # converting those variables into character and then factor
  mutate_at(.vars = vars(any_of(factorVars)), .funs = as.character) %>% 
  mutate_at(.vars = vars(any_of(factorVars)), .funs = as.factor)

# Saving the data into an RDS file
saveRDS(counties, paste0("../Data/counties.rds"))

2.2 Cross Sectional Data

In the code chunk below, we obtain seven additional datasets, whose variables can explain the differences between the time-series of the number of COVID cases per county:

A. Rural/ Underserved Counties: From the Consumer Financial Protection Bureau, we have obtained the Final 2020 List titled: Rural or underserved counties. Per the website, the procedure for determining the classification of a county is as follows: “Beginning in 2020, the rural or underserved counties lists use a methodology for identifying underserved counties described in the Bureau’s interpretive rule: Truth in Lending Act (Regulation Z); Determining “Underserved” Areas Using Home Mortgage Disclosure Act Data.”

B. Based on the US Census Data, we extracted the land area in square miles for each county, which we combined with population to compute each county’s population density, which we hypothesize to be predictive of hotspots for COVID transmission based on the available COVID-19 literature.

C. Based on the MIT Election Data and Science Lab (2018), we have obtained the voting results for all counties in the 2016 Presidential elections. The data was used to compute the percentage of total votes that went to President Trump, with the underlying hypothesis that the politicization of COVID response (e.g., perception/willingness to use face masks, policies and the population’s reaction to the disease) may be explained by party affiliation.

D. We extracted an overall government response index capturing the strength of COVID-19 response policies on a state (and the District of Columbia) level from the Blavatnik School of Government’s GitHub Repository. This index captures 13 different indicators, capturing the ``full range of government response’’. Details for how this indicator is computed can be found at BSG-WP-2020/034.

E. Based on the following Kaiser Health News Webpage, we extracted by county information on the percent of population aged 60+ and the number of ICU beds per Seniors.

F. We have engineered a region variable based on the CDC’s 10 Regions Framework. While geographic regions are hypothesized to be a factor in disease outbreaks, we chose to utilize the CDC regions specifically based on the following explanation from the aforementioned link:
> “CDC’s National Center for Chronic Disease Prevention and Health Promotion (NCCDPHP) is strengthening the consistency and quality of the guidance, communications, and technical assistance provided to states to improve coordination across our state programs”

G. Based on the Census’s Small Area Income and Poverty Estimates (SAIPE) Program, we extracted the estimate for the percent of population in poverty. The estimate is based on 2018 data (released in December 2019). At the time of the start of our analysis, these estimates were the most up to date publicly available data.

crossSectionalData = counties %>% ungroup() %>% 
  select(id, key_numeric, key_google_mobility, population,
         administrative_area_level_2, administrative_area_level_3) %>%
  unique()

# [A] Rural or Urban Classification of the County
ru = read.csv("https://www.consumerfinance.gov/documents/8911/cfpb_rural-underserved-list_2020.csv")
ru %<>%  transmute(key_numeric = FIPS.Code, #renaming FIPS.Code to key_numeric 
                countyType = "Rural/Underserved") # creates two vars and drop old vars
crossSectionalData = merge(crossSectionalData, ru, by = "key_numeric", all.x = TRUE) # to define NA counties
crossSectionalData$countyType %<>% replace_na("Other") # for any county not in the Consumer FIN data replace NA by Other


# [B] Population Density of Each County
download.file("https://www2.census.gov/library/publications/2011/compendia/usa-counties/excel/LND01.xls",
              destfile = "../Data/LND01.xls", mode = "wb") # downloading Land Area Data Per the 2010 Census
areas = read_excel("../Data/LND01.xls") %>% # reading the Excel file
  select(STCOU, LND110210D) #selecting only the FIPS and the Land Area from the 2010 Census variables
colnames(areas) = c("key_numeric", "LandAreaSqMiles2010") # Renaming the columns
areas$key_numeric %<>% as.numeric() # to remove leading 0 

crossSectionalData = merge(crossSectionalData, areas, by ="key_numeric", all.x = TRUE) # adding the area to data frame
crossSectionalData$popDensity = crossSectionalData$population / crossSectionalData$LandAreaSqMiles2010 # creating the population density variable
crossSectionalData %<>% select(-c(population, LandAreaSqMiles2010)) #dropping two variables used in creating pop density 


# [C] 2016 Presidential Elections County Data from Harvard https://doi.org/10.7910/DVN/VOQCHQ
elections = read.csv("../Data/countypres_2000-2016.csv") %>% # reading the downloaded CSV
  filter(year == 2016 & party == "republican") %>% # just keeping data for recent election and republican votes
  mutate(key_numeric = FIPS, # renaming FIPS to key_numeric
         percRepVotes = 100*(candidatevotes/totalvotes) ) %>% # computing percent of republican votes (from total votes)
  select(key_numeric, percRepVotes) # keeping only the key and variable used in merge
crossSectionalData %<>%  merge(elections, by = "key_numeric", all.x = TRUE) # merge with the counties data


# [D] Policy Data
policy = read_csv('https://raw.githubusercontent.com/OxCGRT/USA-covid-policy/master/data/OxCGRT_US_latest.csv')
policy = filter(policy, !is.na(RegionName) | !RegionName %in% c('Alaska', 'Hawaii'))
policy$state = toupper(policy$RegionName) # a state variable = an upper case of existing RegionName
policy$Date %<>% ymd() # converting the Date data to a date format

policySummary = policy %>% # calculating a summary table of median value for the GovernmentResponseIndex per state
  filter(Date >= '2020-03-01' & Date <= endDate) %>% # to match our COVID Data timeSeries
  group_by(state) %>% # perform computations using the median value, per state, for each index
  summarise(GovernmentResponseIndexMedian = median(GovernmentResponseIndex, na.rm = TRUE))
policySummary$state %<>%  str_replace('WASHINGTON DC', 'DISTRICT OF COLUMBIA') %>% str_to_title()

crossSectionalData %<>%  merge(policySummary, by.x = "administrative_area_level_2", by.y = 'state', all.x = TRUE) 


# [E] Kaiser Health News Data on the County Level
hospitals = read.csv("../Data/data-FPBfZ.csv") %>% # downloaded from KHN on 2020-10-26 (~9:30 pm EDT)
  transmute(State = State, # keeping the State Variable | transmute drops variables that are not in call
            County = County, # keeping the County Variable
            PercentSeniors = Percent.of.Population.Aged.60., # Shortening Original Variable Name
            icuBedsPer10000Seniors = 10000 * ICU.Beds/Population.Aged.60.) # Computing icuBedsPer10000Seniors

crossSectionalData %<>% merge(hospitals, 
                              by.x = c("administrative_area_level_2", "administrative_area_level_3"),
                              by.y = c("State", "County"), all.x = TRUE)


# [F] CDC Regions for Each State
regionsCDC = data.frame(States = c('Connecticut', 'Maine', 'Massachusetts', 'New Hampshire', 'Rhode Island' , 
                                   'Vermont', 'New York', # End of Region A
                                   'Delaware', 'District of Columbia', 'Maryland', 'Pennsylvania',
                                   'Virginia', 'West Virginia', 'New Jersey', # End of Region B
                                   'North Carolina', 'South Carolina', 'Georgia', 'Florida', # Region C
                                   'Kentucky', 'Tennessee', 'Alabama', 'Mississippi', # Region D
                                   'Illinois', 'Indiana', 'Michigan', 'Minnesota', 'Ohio',
                                   'Wisconsin', # End of Region E
                                   'Arkansas', 'Louisiana', 'New Mexico', 'Oklahoma', 'Texas', # Region F
                                   'Iowa', 'Kansas', 'Missouri', 'Nebraska', # Region G
                                   'Colorado', 'Montana', 'North Dakota', 'South Dakota',
                                   'Utah', 'Wyoming', # End of Region H
                                   'Arizona', 'California', 'Hawaii', 'Nevada', # Region I
                                   'Alaska', 'Idaho', 'Oregon', 'Washington' # Region J
                                   ),
                        regions = c(rep('A', 7), rep('B', 7), rep('C', 4),
                                    rep('D', 4), rep('E', 6), rep('F', 5),
                                    rep('G', 4), rep('H', 6), rep('I', 4),
                                    rep('J', 4) ) )

crossSectionalData %<>% merge(regionsCDC, by.x = 'administrative_area_level_2', by.y = 'States', all.x = TRUE) # merge


# [G] Poverty Estimates
download.file("https://www2.census.gov/programs-surveys/saipe/datasets/2018/2018-state-and-county/est18all.xls", 
              destfile = "../Data/est18all.xls", mode = "wb") # downloading the data for poverty estimates (latest 2018)

poverty = read_excel("../Data/est18all.xls", skip = 3) %>% # reading the data in R
  transmute(key_numeric = paste0(`State FIPS Code`, `County FIPS Code`) %>% as.numeric, # creating the key from two variables
            povertyPercent = as.numeric(`Poverty Percent, All Ages`) ) # shortening povertyPercent Variable's Name
crossSectionalData %<>% merge(poverty, by = "key_numeric", all.x = TRUE) # merge


# Final Transformations before Saving the Counties Data
crossSectionalData %<>%  mutate_at(.vars = c('countyType', 'regions'), as.factor)  # converting the two vars to factor

# Saving the data into an RDS file
saveRDS(crossSectionalData, paste0("../Data/crossSectionalData.rds"))

# Tabulating the results and providing a way to export the table to different formats
datatable(crossSectionalData %>% select(-c(id, key_numeric, administrative_area_level_2, administrative_area_level_3)),
          extensions = c('FixedColumns', 'Buttons'), options = list(
            dom = 'Bfrtip',
            scrollX = TRUE,
            buttons = c('copy', 'csv', 'excel', 'pdf'),
            fixedColumns = list(leftColumns = 1)),
          rownames = FALSE) %>% 
  formatRound(columns= c('popDensity', 'percRepVotes', 'GovernmentResponseIndexMedian',
                         'PercentSeniors', 'icuBedsPer10000Seniors', 'povertyPercent'),
              digits=1)

2.3 Exploratory Analysis

In this section, we perform an exploratory analysis on the data obtained from the multiple sources.

2.3.1 Cumulative Cases

noGoogleNAs = filter(crossSectionalData, !is.na(key_google_mobility)) # removing NAs from key_google_mobility
idIndex = sample(noGoogleNAs$id, 9) # sampling 9 counties by id

# Saving cumulative deaths figure to an tiff file
tiff(filename = '../Figures/sampleCumulativeCases.tiff',
    width = 1366, height =768, pointsize = 16)
counties %>% filter(id %in% idIndex) %>% 
  ggplot(aes(x = date, y = confirmed, group = id, color = key_google_mobility)) +
  geom_line(size = 1.25) +
  scale_x_date(date_breaks = "1 month", date_labels = "%b") +
  facet_wrap(~ key_google_mobility, scales = 'free_y', ncol = 3) +
  theme(legend.position = 'none') + 
  labs(color = '', x = 'Month', y = 'Cumulative Cases By County',
       caption = paste0('Based on Data from March 01, 2020 - ', endDatePrintV)) +
  scale_color_brewer(type = 'qual', palette = 'Paired')
invisible( dev.off() ) # to suppress the unwanted output from dev.off

# Creating an interactive plot for the markdown
p = ggplot2::last_plot() + geom_line(size = 0.75) + # modifying the plot for plotly
  theme_bw(base_size = 9) + theme(legend.position = 'none') # to make margins smaller
ggplotly(p, height = 768) %>%  layout_ggplotly()

2.3.2 New Daily Cases

# Saving new daily cases figure to an tiff file
tiff(filename = '../Figures/sampleNewDailyCases.tiff',
    width = 1366, height =768, pointsize = 16)
counties %>% filter(id %in% idIndex) %>% 
  ggplot(aes(x = date, y = newCases, group = id, color = key_google_mobility)) +
  geom_line(size = 1.25) +
  scale_x_date(date_breaks = "1 month", date_labels = "%b") +
  facet_wrap(~ key_google_mobility, scales = 'free_y', ncol = 3) +
  theme(legend.position = 'none') + 
  labs(color = '', x = 'Month', y = 'New Daily Cases By County',
       caption = paste0('Based on Data from March 01, 2020 - ', endDatePrintV)) +
  scale_color_brewer(type = 'qual', palette = 'Paired')
invisible( dev.off() ) # to suppress the unwanted output from dev.off

# Creating an interactive plot for the markdown
p = ggplot2::last_plot() + geom_line(size = 0.75) + # modifying the plot for plotly
  theme_bw(base_size = 9) + theme(legend.position = 'none') # to make margins smaller
ggplotly(p, height = 768) %>%  layout_ggplotly()

2.3.3 County Types

crossSectionalData$fips = str_pad(crossSectionalData$key_numeric,
                                     width = 5, side = 'left', pad = '0')
# Retrieving the U.S. county composite map as a simplefeature
cty_sf = counties_sf("aeqd") %>% filter(!state %in% c('Alaska', 'Hawaii')) # from albersua
cty_sf %<>% geo_join(crossSectionalData, by_sp= 'fips', by_df= 'fips')

# Saving a higher quality tiff file for use in the paper
tiff(filename = '../Figures/countyTypes.tiff', width = 1366, height =768, pointsize = 16)
tm_shape(cty_sf) + tm_polygons('countyType', title = 'County Type', palette = "Paired")
invisible( dev.off() ) # to suppress the unwanted output from dev.off

# Printing the png output in the Markdown doc
tm_shape(cty_sf) + tm_polygons('countyType', title = 'County Type', palette = "Paired")

2.3.4 Population Density

# Saving a higher quality tiff file for use in the paper
tiff(filename = '../Figures/popDensity.tiff', width = 1366, height =768, pointsize = 16)
tm_shape(cty_sf) + tm_polygons('popDensity', title = 'Population Density', palette = "Greens",
                               style = 'quantile')
invisible( dev.off() ) # to suppress the unwanted output from dev.off

# Printing the png output in the Markdown doc
tm_shape(cty_sf) + tm_polygons('popDensity', title = 'Population Density', palette = "Greens",
                               style = 'quantile')

2.3.5 Percent Republican Votes

# Saving a higher quality tiff file for use in the paper
tiff(filename = '../Figures/repVotes.tiff', width = 1366, height =768, pointsize = 16)
tm_shape(cty_sf) + tm_polygons('percRepVotes', title = '% Republican Votes', palette = "Reds")
invisible( dev.off() ) # to suppress the unwanted output from dev.off

# Printing the png output in the Markdown doc
tm_shape(cty_sf) + tm_polygons('percRepVotes', title = '% Republican Votes', palette = "Reds")

2.3.6 Government Response Data

state_sf = usa_sf("aeqd") %>% filter(!name %in% c('Alaska', 'Hawaii')) # from albersua
state_sf %<>% geo_join(crossSectionalData, by_sp= 'name', by_df= 'administrative_area_level_2')

# Saving a higher quality tiff file for use in the paper
tiff(filename = '../Figures/govResponse.tiff', width = 1366, height =768, pointsize = 16)
tm_shape(state_sf) + tm_polygons('GovernmentResponseIndexMedian', 
                                 title = 'Median Value of the Government Response Index', palette = "-Greens")
invisible( dev.off() ) # to suppress the unwanted output from dev.off

# Printing the png output in the Markdown doc
tm_shape(state_sf) + tm_polygons('GovernmentResponseIndexMedian', 
                                 title = 'Median Value of the Government Response Index', palette = "-Greens")

2.3.7 Percent Seniors

# Saving a higher quality tiff file for use in the paper
tiff(filename = '../Figures/percSeniors.tiff', width = 1366, height =768, pointsize = 16)
tm_shape(cty_sf) + tm_polygons('PercentSeniors', title = '% Seniors', palette = "Greens")
invisible( dev.off() ) # to suppress the unwanted output from dev.off

# Printing the png output in the Markdown doc
tm_shape(cty_sf) + tm_polygons('PercentSeniors', title = '% Seniors', palette = "Greens")

2.3.8 CDC Regions

# Saving a higher quality tiff file for use in the paper
tiff(filename = '../Figures/cdcRegions.tiff', width = 1366, height =768, pointsize = 16)
tm_shape(state_sf) + tm_polygons('regions', title = 'CDC Region', palette = "Paired")
invisible( dev.off() ) # to suppress the unwanted output from dev.off

# Printing the png output in the Markdown doc
tm_shape(state_sf) + tm_polygons('regions', title = 'CDC Region', palette = "Paired")

2.3.9 Percent Poverty

# Saving a higher quality tiff file for use in the paper
tiff(filename = '../Figures/povertyPercent.tiff', width = 1366, height =768, pointsize = 16)
tm_shape(cty_sf) + tm_polygons('povertyPercent', title = 'Poverty Percent', palette = "Greens")
invisible( dev.off() ) # to suppress the unwanted output from dev.off

# Printing the png output in the Markdown doc
tm_shape(cty_sf) + tm_polygons('povertyPercent', title = 'Poverty Percent', palette = "Greens")

3 Time-Series Clustering

It is important to note that, in our estimation, there are three important decisions to be made when performing time-series clustering:

  • Preparation of the Different Time-Series to be Clustered In this section, we have (a) selected the new daily cases per county as the primary variable of interest, (b) smoothed that variable using a seven-day moving average, and (c) scaled the observations within each county’s 7-day MA of new daily deaths such that it is bounded between 0 and 1. This allows us to compare the shape of the time-series/profile across counties of different populations and where the magnitude of the cases is quite different.

  • Choice of Distance Measure: The Euclidean distance, The Euclidean Distance i.e., the \(l_2\) norm, is the most commonly used distance measure since it is computationally efficient. However, it may not be suitable for applications where the time-series are of different length in addition to being sensitive to noise, scale and time shifts (Sardá-Espinosa, 2017).

  • Choice of Clustering Algorithm: A large number of clustering algorithms have been proposed in the literature. Most common clustering approaches are shape-based, which include \(k-\)means clustering and hierarchical clustering. The reader is referred to Aghabozorgi et al. (2015) for a detailed review. In our preliminary analysis, we have chosen to use the hierarchical clustering approach since it provides an easy to understand dendogram and the number of counties was small. However, in our full analysis, we will use the \(k-\)means clustering algorithm since it is computationally efficient. Furthermore, we overcame the traditional limitation of having to pre-specify \(k\) by utilizing 26 indexes for determining the optimal number of clusters in a data set based on the excellent approach and package implementation of Charrad et al. (2014).

3.1 Data Preparation

clusteringPrep = counties %>% # from the counties
  select(id, date, key_google_mobility, newCases) %>% # selecting minimal amount of cols for visual inspection
  arrange(id, date) %>% # arranged to ensure correct calculations
  mutate(newMA7 = rollmeanr(newCases, k = 7, fill = NA), # 7-day ma of new (adjusted) cases
         maxMA7 = max(newMA7, na.rm = T), # obtaining the max per county to scale data
         scaledNewMA7 = pmax(0, newMA7/maxMA7, na.rm = TRUE) ) %>% # scaling data to a 0-1 scale by county
  select(id, key_google_mobility, date, scaledNewMA7) %>% # dropping the variable newCases
  pivot_wider(names_from = date, values_from = scaledNewMA7) # converting the data to a wide format for clustering

constantColumns  = which_are_constant(clusteringPrep, verbose = F) # identifying constant columns
datesDropped = colnames(clusteringPrep)[constantColumns] # used for printing the names after the code chunk

clusteringPrep %<>% select(-all_of(constantColumns) ) %>%  # speeds up clustering by dec length of series
  as.data.frame() # data needs to be data frame for clustering
row.names(clusteringPrep) = clusteringPrep[,1] # needed for tsclust
clusteringPrep = clusteringPrep[,-1] # dropping the id column since it is now row.name

The following dates were removed from our data frame since the scaledNEWMA7 variable was constant across all counties: 2020-03-01, 2020-03-02, 2020-03-03, 2020-03-04, 2020-03-05, 2020-03-06 and 2020-03-07.

3.2 Clustering Contiguous U.S. Counties

clusteringPrep %<>% select(-c(key_google_mobility))  # removing this variable so we can cluster

nc  = NbClust(clusteringPrep, distance = "euclidean", # euclidean distance
             min.nc = 2, max.nc = 49, # searching for optimal k between k=2 and k=49
             method = "kmeans", # using the k-means method
             index = "all") # using 26 of the 30 indices in the package
## *** : The Hubert index is a graphical method of determining the number of clusters.
##                 In the plot of Hubert index, we seek a significant knee that corresponds to a 
##                 significant increase of the value of the measure i.e the significant peak in Hubert
##                 index second differences plot. 
## 
## *** : The D index is a graphical method of determining the number of clusters. 
##                 In the plot of D index, we seek a significant knee (the significant peak in Dindex
##                 second differences plot) that corresponds to a significant increase of the value of
##                 the measure. 
##  
## ******************************************************************* 
## * Among all indices:                                                
## * 6 proposed 2 as the best number of clusters 
## * 3 proposed 3 as the best number of clusters 
## * 7 proposed 4 as the best number of clusters 
## * 1 proposed 6 as the best number of clusters 
## * 1 proposed 11 as the best number of clusters 
## * 2 proposed 13 as the best number of clusters 
## * 1 proposed 27 as the best number of clusters 
## * 1 proposed 44 as the best number of clusters 
## * 1 proposed 49 as the best number of clusters 
## 
##                    ***** Conclusion *****                            
##  
## * According to the majority rule, the best number of clusters is  4 
##  
##  
## *******************************************************************
kclus  = nc$Best.partition %>% as.data.frame() %>% #obtaining the best partition/ cluster assignment for optimal k
  rename(., cluster_group = .) %>% rownames_to_column("County") 

#converting the wide to tall data and adding the cluster groupings
clusters  = clusteringPrep %>% 
  rownames_to_column(var = "County") %>% 
  pivot_longer(cols = starts_with("2020"), names_to = "Date") %>% 
  inner_join(., kclus, by = "County") %>% 
  mutate(cluster_group = as.factor(cluster_group))

idClusters  = clusters %>% select(c(County, cluster_group)) # creating a look-up table of county and cluster group
colnames(idClusters)  = c('id', 'cluster_group') # renaming the columns
idClusters %<>%  unique() #removing the duplicates due to different dates (we had that to ensure that the clustering was applied correctly)

# Adding Cluster Grouping to a subset of the counties data frame
clusterCounties = counties %>% 
  select(c(id, key_numeric, key_google_mobility, administrative_area_level_2, administrative_area_level_3)) %>% 
  inner_join(., idClusters, by ='id') %>% 
  mutate(cluster_group = paste0('C', cluster_group)) %>% 
  unique()

# saving the results as a RDS File
saveRDS(clusterCounties, '../Data/clusterCounties.rds')

3.3 Visualizing the Clustering Results

In this subsection, we provide three plots:

  • A paneled spaghetti plot, highlighting the median scaled time-series for profile for each cluster;
  • A panel plot where the first, second and third quartiles of the scaled time-series for each cluster are compared; and
  • An interactive chloropleth maps to visualize the spatial distribution of the clusters, where the reader can click on a given county to show: (a) county name, (b) assigned cluster, (c) population density, and (d) percentage of residents in poverty.

3.3.1 Spaghetti Plot

spaghettiDF = counties %>% # from the counties
  select(id, date, newCases, key_google_mobility) %>% # selecting minimal columns
  left_join(clusterCounties[, c('id', 'cluster_group')], by = 'id') %>% # to get clusters
  arrange(id, date) %>% # arranged to ensure correct calculations
  mutate(newMA7 = rollmeanr(newCases, k = 7, fill = NA), # 7-day ma of new (adjusted) deaths
         maxMA7 = max(newMA7, na.rm = T), # obtaining the max per county to scale data
         scaledNewMA7 = pmax(0, newMA7/maxMA7, na.rm = TRUE) ) %>% 
  ungroup() %>% select(date, cluster_group, scaledNewMA7, key_google_mobility) %>% 
  group_by(date, cluster_group)

spaghettiDF$cluster_group %<>% as.factor() 

# Creating a Named Color Scale
colorPal =  brewer.pal(n= levels(spaghettiDF$cluster_group) %>% length(), 'Set2')
names(colorPal) = levels(spaghettiDF$cluster_group)

# Saving spaghetti plot to an tiff file
tiff(filename = '../Figures/spaghettiPlot.tiff', width = 1366, height =768, pointsize = 16)
spaghettiDF %>%  
  ggplot(aes(x = date, y = scaledNewMA7, color = cluster_group, group = key_google_mobility)) +
  scale_x_date(date_breaks = "1 month", date_labels = "%b") +
  geom_line(size = 0.25, alpha = 0.1) +
  stat_summary(aes(group = 1), 
               fun= median,
               geom = "line",
               size = 1.25, col = 'black') + 
  facet_wrap(~ cluster_group, ncol = 1) +
  theme(legend.position = 'none') + 
  labs(x = 'Month', y = 'Scaled New Cases By Cluster By Day',
       caption = paste0('Solid black line represents the median for each cluster | 
       Based on Data from March 01, 2020 - ', endDatePrintV) )  +
  scale_color_manual(values = colorPal)
invisible( dev.off() ) # to suppress the unwanted output from dev.off

# Printing a png version of the plot in Markdown (lower quality image for quicker compilation of HTML)
readTIFF("../Figures/spaghettiPlot.tiff") %>% grid.raster()

3.3.2 Summary Plot

# Creating a data frame containing statistical summaries of the time series by cluster_group
summaryDf = spaghettiDF %>% 
  summarise(Median = median(scaledNewMA7, na.rm= TRUE),
            `First Quartile` = quantile(scaledNewMA7, probs = 0.25, na.rm= TRUE),
            `Third Quartile` = quantile(scaledNewMA7, probs = 0.75, na.rm= TRUE)) %>% 
  pivot_longer(cols = c(`First Quartile`, Median, `Third Quartile`),
                        names_to = 'Statistic')

tiff(filename = '../Figures/summaryPlot.tiff', width = 1366, height =768, pointsize = 16)
summaryDf %>% 
  ggplot(aes(x = date, y = value, color = cluster_group, linetype =  Statistic)) +
  scale_x_date(date_breaks = "1 month", date_labels = "%b") +
  geom_line(size = 1.25) +
   scale_linetype_manual(values = c('dotted', 'solid', 'twodash')) +
  facet_wrap(~ cluster_group, ncol = 1) +
  theme(legend.position = 'top') + 
  labs(color = '', x = 'Month', y = 'Quartiles of Scaled New Cases By Cluster By Day',
       caption = paste0('Based on Data from March 01, 2020 - ', endDatePrintV)) +
  scale_color_manual(values = colorPal)
invisible( dev.off() ) # to suppress the unwanted output from dev.off

# Printing a png version of the plot in Markdown (lower quality image for quicker compilation of HTML)
readTIFF("../Figures/summaryPlot.tiff") %>% grid.raster()

3.3.3 Cluster Map

# Joining the clusterCounties results with the existing county simple features object (cty_sf)
clusterCounties$fips = str_pad(clusterCounties$key_numeric, width = 5, side = 'left', pad = '0')
clusterCounties %<>% ungroup()
cty_sf %<>% left_join(clusterCounties[, c('fips', 'cluster_group')], by = 'fips') # adding cluster_group to cty_sf

# Creating a static visual for the paper
tiff(filename = '../Figures/clusterMap.tiff', width = 1366, height =768, pointsize = 16)
tm_shape(cty_sf) + tm_polygons('cluster_group', title = 'Cluster #', palette = colorPal) +
  tm_credits(paste0('Based on Data from March 01, 2020 - ', endDatePrintV), position=c("right", "bottom"))
invisible( dev.off() ) # to suppress the unwanted output from dev.off


# Creating an interactive visual Using the Leaflet Package
#### Creating a longlat projection (required by leaflet)
leaflet_sf = counties_sf("longlat") %>% filter(!state %in% c('Alaska', 'Hawaii')) # from albersua
leaflet_sf %<>% geo_join(crossSectionalData, by_sp= 'fips', by_df= 'fips') %>% 
  left_join(clusterCounties[, c('fips', 'cluster_group')], by = 'fips')

#### Setting the Color Scheme
leafletPal =  colorFactor('Set2', domain = leaflet_sf$cluster_group, na.color = "white")

#### The visual
leaflet(height=500) %>% # initializing the leaflet map
  setView(lng = -96, lat = 37.8, zoom = 3.8) %>% # setting the view on Continental US
  addTiles() %>% # adding the default tiles
  addPolygons(data = leaflet_sf, stroke = FALSE, fillColor = ~leafletPal(leaflet_sf$cluster_group), # adding the data
              weight = 2, opacity = 1, color = "white", dashArray = "3", fillOpacity = 0.7, # adding color specs
              popup = paste("County:", leaflet_sf$name, '<br>', 
                            "Cluster #:", leaflet_sf$cluster_group, '<br>',
                            "Population Density:", round(leaflet_sf$popDensity, 1), '<br>')) %>% #pop-up Menu
  addLegend(position = "bottomleft", pal = leafletPal, values =  leaflet_sf$cluster_group, 
            title = "Cluster #", opacity = 1) # legend formatting

4 Explanatory Modeling of Cluster Assignments

In the previous section, we showed that by using solely a scaled and smoothed time series of daily cases per county, the counties are grouped into categories (whose time-series have distinct shapes based on the Euclidean distance measure). In this section, we attempt to model the factors that are associated with the cluster assignment.

4.1 Descriptive Statistics

multiClassDF = select(clusterCounties, id, cluster_group) %>% 
  left_join(crossSectionalData, by = 'id')  %>% 
  select(-c(administrative_area_level_2, administrative_area_level_3, id, key_numeric))

saveRDS(multiClassDF, '../Data/multiClassDF.rds') # saving the data

skim(multiClassDF) # printing a nice summary table of the data
Data summary
Name multiClassDF
Number of rows 3108
Number of columns 11
_______________________
Column type frequency:
character 3
factor 2
numeric 6
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
cluster_group 0 1.00 2 2 0 4 0
key_google_mobility 195 0.94 14 35 0 2913 0
fips 0 1.00 5 5 0 3108 0

Variable type: factor

skim_variable n_missing complete_rate ordered n_unique top_counts
countyType 0 1 FALSE 2 Rur: 1596, Oth: 1512
regions 0 1 FALSE 10 E: 524, F: 503, G: 412, C: 372

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
popDensity 1 1.00 369.63 6667.59 0.24 17.28 45.31 119.57 365169.38 ▇▁▁▁▁
percRepVotes 1 1.00 63.30 15.65 4.09 54.48 66.35 74.92 96.03 ▁▂▅▇▃
GovernmentResponseIndexMedian 1 1.00 47.57 7.86 26.94 44.30 47.22 50.66 70.28 ▁▃▇▂▁
PercentSeniors 36 0.99 24.85 5.52 5.80 21.30 24.45 27.80 64.20 ▁▇▂▁▁
icuBedsPer10000Seniors 36 0.99 5.49 9.43 0.00 0.00 0.00 8.71 248.16 ▇▁▁▁▁
povertyPercent 0 1.00 15.18 6.11 2.60 10.90 14.15 18.30 54.00 ▆▇▂▁▁

4.2 Boxplot By Cluster

multiClassDF %>% plot_boxplot(by = 'cluster_group', ncol = 2L, 
               ggtheme = theme_bw(),
               geom_boxplot_args = list('outlier.shape' = 1))

4.3 Explanatory Modeling Using Multinomial Spatial Regression

4.3.1 Data Preparation

multiClassDF$cluster_group %<>% as.factor() # convert to a factor

# impute without using cluster_group, key_google_mobility and fips
multiClassImputed = VIM::kNN(multiClassDF, imp_var = FALSE,
                             dist_var = colnames(multiClassDF)[3:10])
saveRDS(multiClassImputed, '../Data/multiClassImputed.rds') # saving the data

# Creating a df (which will be used for analysis)
df = multiClassImputed # setting df to equal to the multiclass object
df$clustReLeveled =  relevel(df$cluster_group, ref = maxCat(df$cluster_group) ) # setting the ref level
df  = df %>% select(-c(cluster_group, # removed since it is now redundant with the clustReLeveled variable
                       key_google_mobility, # removed since they are identifier variables
                       icuBedsPer10000Seniors, percRepVotes)) # did not sig. improve predictions
saveRDS(df, '../Data/df.rds') # saving the data

4.3.2 Model Building

multiClassDF.sorted = df[order(df$fips), ]

# File needs to be downloaded from 
# https://www2.census.gov/geo/tiger/TIGER2016/COUNTY/tl_2016_us_county.zip
shape =  readOGR('J:/My Drive/Miami/Code/GitHub/covid/Data', "tl_2016_us_county")
## OGR data source with driver: ESRI Shapefile 
## Source: "J:\My Drive\Miami\Code\GitHub\covid\Data", layer: "tl_2016_us_county"
## with 3233 features
## It has 17 fields
## Integer64 fields read as strings:  ALAND AWATER
shape_sp = st_as_sf(shape)

# Joining both the clustered data and the shape file in one object
joinedMultiClass = geo_join(shape_sp, multiClassDF.sorted, by_sp = 'GEOID', by_df ='fips',how = 'inner')
joinedMultiClass.sorted = joinedMultiClass[order(joinedMultiClass$GEOID), ]

# Building a neighborhood list from the shape data
nbList = poly2nb(joinedMultiClass.sorted) %>% 
  nb2listw(style = 'W', zero.policy = TRUE) # converting it to a listw object

# Identifying the number of neighbors per county
numCounties = length(nbList$neighbours) # number of counties
numNeighbors = rep(0, numCounties) # number of neighbors for each county
for (i in 1:numCounties)  numNeighbors[i] = length(nbList$neighbours[[i]])

# Identifying the fips of the neighbor counties
fipsOfNeighbourCounties = c() # initialization
for (i in 1:numCounties) fipsOfNeighbourCounties = c(fipsOfNeighbourCounties, nbList$neighbours[[i]] )

# Pre-nimble parameters
sumNumNeigh = length(fipsOfNeighbourCounties)
m = length(numNeighbors)

# Preparing the outcome
number_of_clusters = unique(multiClassDF.sorted$clustReLeveled) %>% length()
cluster_group_matrix = matrix( 0 , nrow = m, ncol = number_of_clusters)
cluster_group_nu = str_remove(multiClassDF.sorted$clustReLeveled, 'C') %>% as.numeric()

for (i in 1:m)
{
  cluster_group_matrix[ i , cluster_group_nu[i] ] = 1
}

## Storing each predictor in a vector
countyType = multiClassDF.sorted$countyType
Underserved = fastDummies::dummy_cols(countyType)
Underserved.rural = Underserved$`.data_Rural/Underserved` %>% as.matrix() %>% as.vector()
popDensity = multiClassDF.sorted$popDensity %>% as.matrix() %>% as.vector()
GovernmentResponseIndexMedian = multiClassDF.sorted$GovernmentResponseIndexMedian %>% as.matrix() %>% as.vector()
PercentSeniors = multiClassDF.sorted$PercentSeniors %>% as.matrix() %>% as.vector()
povertyPercent = multiClassDF.sorted$povertyPercent %>% as.matrix() %>% as.vector()
regions = multiClassDF.sorted$regions

results = dummy_cols(regions)
A = results$.data_A
B = results$.data_B
C = results$.data_C
D = results$.data_D
E = results$.data_E
FF = results$.data_F
G = results$.data_G
H = results$.data_H
I = results$.data_I



# Nimble Code spatial
code = nimbleCode(
  {
    for (i in 1:m)
    {
      cluster_group_matrix[i,1:number_of_clusters] ~ dmulti( prob = p[i,1:number_of_clusters] ,1) 
      phi[i,1] <- 1
      p[i,1] <- 1/sum(phi[i,1:number_of_clusters])
      for (k in 2:number_of_clusters)
      {
        log(phi[i,k]) <- b0[k] + b1[k]*Underserved.rural[i] + 
          b2[k]*popDensity[i] +
          b3[k]*PercentSeniors[i] + 
          b4[k]*GovernmentResponseIndexMedian[i] + 
          b5[k]*povertyPercent[i] + b6[k]*A[i] + b7[k]*B[i] + 
          b8[k]*C[i] + b9[k]*D[i] + b10[k]*E[i] + b11[k]*F[i] + b12[k]*G[i] + b13[k]*H[i] + b14[k]*I[i] + u[i]
        p[i,k] <- phi[i,k]/sum(phi[i,1:number_of_clusters])
      }
    }    
    for (k in 2:number_of_clusters) 
    {
      b0[k] ~ dnorm(0, 0.00001); b1[k] ~ dnorm(0, 0.00001); b2[k] ~ dnorm(0, 0.00001)
      b3[k] ~ dnorm(0, 0.00001); b4[k] ~ dnorm(0, 0.00001); b5[k] ~ dnorm(0, 0.00001)
      b6[k] ~ dnorm(0, 0.00001); b7[k] ~ dnorm(0, 0.00001); b8[k] ~ dnorm(0, 0.00001)
      b9[k] ~ dnorm(0, 0.00001); b10[k] ~ dnorm(0, 0.00001); b11[k] ~ dnorm(0, 0.00001)
      b12[k] ~ dnorm(0, 0.00001); b13[k] ~ dnorm(0, 0.00001); b14[k] ~ dnorm(0, 0.00001)
    }
    u[1:m] ~ dcar_normal(adj[1:sumNumNeigh], weights[1:sumNumNeigh], 
                         num[1:m],tauu)
    for (j in 1:sumNumNeigh)
    {weights[j] <- 1}
    tauu ~ dgamma(1,0.0001)
  }
)  

constants = list(num=numNeighbors, adj=fipsOfNeighbourCounties,
                  sumNumNeigh = length(fipsOfNeighbourCounties), 
                  m=m,number_of_clusters=number_of_clusters)

data = list(cluster_group_matrix = cluster_group_matrix, 
             Underserved.rural = Underserved.rural,
             popDensity = popDensity,
             PercentSeniors = PercentSeniors,
             GovernmentResponseIndexMedian = GovernmentResponseIndexMedian,
             povertyPercent = povertyPercent,
             A=A, B=B, C=C, D=D, E=E, F=FF,
             G=G, H=H, I=I)

inits = list(b0=rep(0, number_of_clusters), u=rep(0,m),tauu=1, b1=rep(0, number_of_clusters), 
             b2=rep(0, number_of_clusters), b3=rep(0, number_of_clusters), b4=rep(0, number_of_clusters),
             b5=rep(0, number_of_clusters), b6=rep(0, number_of_clusters), b7=rep(0, number_of_clusters), 
             b8=rep(0, number_of_clusters), b9=rep(0, number_of_clusters), b10=rep(0, number_of_clusters),
             b11=rep(0, number_of_clusters), b12=rep(0, number_of_clusters), b13=rep(0, number_of_clusters),
             b14=rep(0, number_of_clusters) )

Rmodel = nimbleModel(code=code, constants=constants, data=data, inits=inits)

compile.Rmodel = compileNimble( Rmodel )

monitors = c('b0','b1','b2','b3','b4','b5','b6','b7','b8',
              'b9','b10','b11','b12','b13','b14','p','tauu')

Rmodel.Conf = configureMCMC( Rmodel , monitors=monitors, thin = 100)
## ===== Monitors =====
## thin = 100: b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, p, tauu
## ===== Samplers =====
## RW sampler (46)
##   - b0[]  (3 elements)
##   - b1[]  (3 elements)
##   - b2[]  (3 elements)
##   - b3[]  (3 elements)
##   - b4[]  (3 elements)
##   - b5[]  (3 elements)
##   - b6[]  (3 elements)
##   - b7[]  (3 elements)
##   - b8[]  (3 elements)
##   - b9[]  (3 elements)
##   - b10[]  (3 elements)
##   - b11[]  (3 elements)
##   - b12[]  (3 elements)
##   - b13[]  (3 elements)
##   - b14[]  (3 elements)
##   - tauu
## CAR_normal sampler (1)
##   - u[1:3108]
Rmodel.MCMC = buildMCMC( Rmodel.Conf )
compile.Rmodel.MCMC = compileNimble( Rmodel.MCMC )

niter = 300000
nburn = 150000

start.time = proc.time()
spatial.base6 = runMCMC( compile.Rmodel.MCMC, niter = niter, nburnin = nburn,
                         inits = inits, nchains = 1, samplesAsCodaMCMC = TRUE )
## |-------------|-------------|-------------|-------------|
## |-------------------------------------------------------|
stop.time = proc.time()
time.elapsed = stop.time - start.time
print( time.elapsed )
##     user   system  elapsed 
## 80161.21     2.61 80199.27

4.4 Resulting Model

Betacoe = spatial.base6[, (1:60)] # based on current number of predictors
saveRDS(Betacoe, '../Data/betacoe.rds') # saving the data

# Computing the coefficients' values
coeffTable = rbind(colMeans(Betacoe), colSds(Betacoe))
rownames(coeffTable) = c('means', 'stdevs')

# Formating the output
coeffTable %<>% as.data.frame() %>% 
  select(paste0(paste0("b", rep(seq(0, 14), 4)), '[', 
                c(rep(1,15), rep(2,15), rep(3,15), rep(4,15)),
                ']' )) %>% # reordering cols by name
  select_if(~ !any(is.na(.)))  # dropping NA cols (corresponding to Cluster 1)

tCoeffTable = t(coeffTable)

tCoeffTable = cbind(tCoeffTable[1:15,], tCoeffTable[16:30,], tCoeffTable[31:45,]) %>% data.frame()
row.names(tCoeffTable) = c('constant', 'rural', 'popDensity', 'percSeniors',
                           'govResponse', 'percPoverty', 'regionA', 'regionB', 'regionC',
                           'regionD', 'regionE', 'regionF', 'regionG', 'regionH',
                           'regionI')
colnames(tCoeffTable) = c('C2_coef_mean', 'C2_coef_sd',
                          'C3_coef_mean', 'C3_coef_sd',
                          'C4_coef_mean', 'C4_coef_sd')

tCoeffTable %>% round(digits = 3) %>% datatable(
          extensions = c('FixedColumns', 'Buttons'), options = list(
            pageLength = 15,
            dom = 'Bfrtip',
            scrollX = TRUE,
            buttons = c('copy', 'csv', 'excel', 'pdf'),
            fixedColumns = list(leftColumns = 1)))

4.5 Model’s Performance

samples_p = spatial.base6[, -(1:60)]
samples_p_mean = colMeans(exp(samples_p[, 1:(number_of_clusters*m)]))
C1 = samples_p_mean[1:3108]
C2 = samples_p_mean[3109:6216]
C3 = samples_p_mean[6217:9324]
C4 = samples_p_mean[9325:12432]
pred.0 = cbind(C1, C2, C3, C4) 
pred = rep(NA, m)
for (i in 1:m) {
  vec = pred.0[i, 1:number_of_clusters]
  pred[i] = which.max(vec)
}

predicted.spatial = cbind(pred, multiClassDF.sorted[, 'fips']) 
colnames(predicted.spatial) = c('pred', 'fips')

predSpatialFinal = merge(predicted.spatial, multiClassDF.sorted[, c('clustReLeveled', 'fips')],
                         by = 'fips')

predSpatialFinal$clustReLeveled %<>%  str_remove('C')
saveRDS(predSpatialFinal, '../Data/predSpatialFinal.rds') # saving the data


# Computing the Confusion Metrics and By Class Metrics
confMatrix = confusionMatrix(as.factor(predSpatialFinal$pred), 
                             as.factor(predSpatialFinal$clustReLeveled))
saveRDS(confMatrix, '../Data/confMatrixSpatialModel.rds') # saving the data

# Printing the Resulting tables nicely
pander(confMatrix$table)
1 2 3 4
1017 0 0 0
0 355 96 118
0 53 267 150
0 154 168 730
pander(confMatrix$byClass)
Table continues below
  Sensitivity Specificity Pos Pred Value Neg Pred Value
Class: 1 1 1 1 1
Class: 2 0.6317 0.9159 0.6239 0.9185
Class: 3 0.5028 0.9212 0.5681 0.8999
Class: 4 0.7315 0.8474 0.6939 0.8696
Table continues below
  Precision Recall F1 Prevalence Detection Rate
Class: 1 1 1 1 0.3272 0.3272
Class: 2 0.6239 0.6317 0.6278 0.1808 0.1142
Class: 3 0.5681 0.5028 0.5335 0.1708 0.08591
Class: 4 0.6939 0.7315 0.7122 0.3211 0.2349
  Detection Prevalence Balanced Accuracy
Class: 1 0.3272 1
Class: 2 0.1831 0.7738
Class: 3 0.1512 0.712
Class: 4 0.3385 0.7894
pander(confMatrix$overall)
Table continues below
Accuracy Kappa AccuracyLower AccuracyUpper AccuracyNull
0.7622 0.6722 0.7469 0.7771 0.3272
AccuracyPValue McnemarPValue
0 NA

4.6 Visualizing the Model’s Outcomes

###Visualizing the Model’s Predictions
predSpatialFinal = readRDS('../Data/predSpatialFinal.rds') # saving the data
predSpatialFinal$match = ifelse(predSpatialFinal$pred == predSpatialFinal$clustReLeveled, "Yes", "No") %>%
  as.factor()

mapResults = predSpatialFinal
mapResults$fips %<>% as.factor()

# Retrieving the U.S. county composite map as a simplefeature (since it has been overwritten)
counties_sf = counties_sf("aeqd") %>% filter(!state %in% c('Alaska', 'Hawaii')) # from albersua
counties_sf %<>% geo_join(mapResults, by_sp= 'fips', by_df= 'fips')


# Creating a static visual for use in the paper
tiff(filename = '../Figures/clusterMatchMapSpatial.tiff', width = 1366, height =768, pointsize = 16)
tm_shape(counties_sf) + tm_polygons('match', title = 'Cluster Match', style = 'cont', palette = "div") +
  tm_layout(aes.palette = list(div = list("Yes" = "#CAB2D6", "No" = "#6A3D9A"))) +
  tm_credits(paste0('Based on Data from March 01, 2020 - ', endDatePrintV), position=c("right", "bottom"))
invisible( dev.off() ) # to suppress the unwanted output from dev.off


# Creating a longlat projection (required by leaflet)
leaflet_sf = counties_sf("longlat") %>% filter(!state %in% c('Alaska', 'Hawaii')) # from albersua
leaflet_sf %<>% geo_join(mapResults, by_sp= 'fips', by_df= 'fips')

leafletPal = colorFactor(palette = c("#CAB2D6", "#6A3D9A"), levels = c('Yes', 'No'), na.color = "white")

leaflet(height = 500) %>% 
  setView(lng = -96, lat = 37.8, zoom = 3.8) %>%
  addTiles() %>%
  addPolygons(data = leaflet_sf, stroke = FALSE, fillColor = ~leafletPal(leaflet_sf$match),
              weight = 2, opacity = 1, color = "white", dashArray = "3", fillOpacity = 0.7,
              popup = paste("County:", leaflet_sf$name, '<br>', 
                            "Cluster #:", leaflet_sf$clustReLeveled, '<br>',
                            "Cluster Predicted:", leaflet_sf$pred, '<br>',
                            "Cluster Match:", leaflet_sf$match, '<br>')) %>% #pop-up Menu
  addLegend(position = "bottomleft", pal = leafletPal, values =  leaflet_sf$match, 
            title = "Cluster Match", opacity = 1)

5 References

Aghabozorgi, S., Shirkhorshidi, A. S., & Wah, T. Y. (2015). Time-series clustering–a decade review. Information Systems, 53, 16–38.

Charrad, M., Ghazzali, N., Boiteau, V., & Niknafs, A. (2014). NbClust: An r package for determining the relevant number of clusters in a data set. Journal of Statistical Software, Articles, 61(6), 1–36. https://doi.org/10.18637/jss.v061.i06

Guidotti, E., & Ardia, D. (2020). COVID-19 data hub. Journal of Open Source Software, 5(51), 2376. https://doi.org/10.21105/joss.02376

MIT Election Data and Science Lab. (2018). County Presidential Election Returns 2000-2016 (Version V6) [Data set]. Harvard Dataverse. https://doi.org/10.7910/DVN/VOQCHQ

Sardá-Espinosa, A. (2017). Comparing time-series clustering algorithms in r using the dtwclust package. R Package Vignette, 12, 41.


6 Appendices

6.1 Appendix A: Packages Used

In the appendix, we print all the R packages used in our analysis and their versions to assist with reproducing our results/analysis.

pander(sessionInfo(), compact = TRUE) # printing the session information

R version 4.0.3 (2020-10-10)

Platform: x86_64-w64-mingw32/x64 (64-bit)

locale: LC_COLLATE=English_United States.1252, LC_CTYPE=English_United States.1252, LC_MONETARY=English_United States.1252, LC_NUMERIC=C and LC_TIME=English_United States.1252

attached base packages: grid, stats, graphics, grDevices, utils, datasets, methods and base

other attached packages: conflicted(v.1.0.4), caret(v.6.0-86), lattice(v.0.20-41), nnet(v.7.3-14), matrixStats(v.0.57.0), fastDummies(v.1.6.3), nimble(v.0.10.1), spdep(v.1.1-5), sf(v.0.9-6), spData(v.0.3.8), rgdal(v.1.5-18), sp(v.1.4-4), VIM(v.6.0.0), colorspace(v.2.0-0), NbClust(v.3.0), expsmooth(v.2.3), fma(v.2.4), forecast(v.8.13), fpp2(v.2.4), zoo(v.1.8-8), tmap(v.3.2), leaflet(v.2.0.3), tigris(v.1.0), plotly(v.4.9.2.2), tiff(v.0.1-6), DataExplorer(v.0.8.2), RColorBrewer(v.1.1-2), scales(v.1.1.1), knitr(v.1.30), stargazer(v.5.2.2), pander(v.0.6.3), DT(v.0.16), readxl(v.1.3.1), rvest(v.0.3.6), xml2(v.1.3.2), COVID19(v.2.3.1), skimr(v.2.1.2), dataPreparation(v.1.0.1), progress(v.1.2.2), Matrix(v.1.2-18), lubridate(v.1.7.9.2), janitor(v.2.0.1), magrittr(v.2.0.1), forcats(v.0.5.0), stringr(v.1.4.0), dplyr(v.1.0.2), purrr(v.0.3.4), readr(v.1.4.0), tidyr(v.1.1.2), tibble(v.3.0.4), tidyverse(v.1.3.0), albersusa(v.0.4.1), devtools(v.2.3.2), usethis(v.2.0.0), pacman(v.0.5.1), lemon(v.0.4.5) and ggplot2(v.3.3.3)

loaded via a namespace (and not attached): tidyselect(v.1.1.0), htmlwidgets(v.1.5.3), ranger(v.0.12.1), maptools(v.1.0-2), pROC(v.1.16.2), munsell(v.0.5.0), codetools(v.0.2-16), units(v.0.6-7), withr(v.2.3.0), uuid(v.0.1-4), rstudioapi(v.0.13), stats4(v.4.0.3), robustbase(v.0.93-6), vcd(v.1.4-8), TTR(v.0.24.2), repr(v.1.1.0), farver(v.2.0.3), rprojroot(v.2.0.2), LearnBayes(v.2.15.1), coda(v.0.19-4), vctrs(v.0.3.6), generics(v.0.1.0), ipred(v.0.9-9), xfun(v.0.19), R6(v.2.5.0), assertthat(v.0.2.1), networkD3(v.0.4), rgeos(v.0.5-5), gtable(v.0.3.0), lwgeom(v.0.2-5), processx(v.3.4.5), timeDate(v.3043.102), rlang(v.0.4.9), splines(v.4.0.3), lazyeval(v.0.2.2), ModelMetrics(v.1.2.2.2), dichromat(v.2.0-0), broom(v.0.7.3), reshape2(v.1.4.4), yaml(v.2.2.1), abind(v.1.4-5), modelr(v.0.1.8), crosstalk(v.1.1.0.1), backports(v.1.2.1), quantmod(v.0.4.18), lava(v.1.6.8.1), tools(v.4.0.3), ellipsis(v.0.3.1), raster(v.3.4-5), sessioninfo(v.1.1.1), Rcpp(v.1.0.5), plyr(v.1.8.6), base64enc(v.0.1-3), classInt(v.0.4-3), ps(v.1.5.0), prettyunits(v.1.1.1), rpart(v.4.1-15), deldir(v.0.2-3), fracdiff(v.1.5-1), tmaptools(v.3.1), haven(v.2.3.1), fs(v.1.5.0), leafem(v.0.1.3), data.table(v.1.13.6), openxlsx(v.4.2.3), gmodels(v.2.18.1), lmtest(v.0.9-38), reprex(v.0.3.0), pkgload(v.1.1.0), hms(v.0.5.3), evaluate(v.0.14), XML(v.3.99-0.5), rio(v.0.5.16), gridExtra(v.2.3), testthat(v.3.0.1), compiler(v.4.0.3), KernSmooth(v.2.23-17), crayon(v.1.3.4), htmltools(v.0.5.0), expm(v.0.999-5), DBI(v.1.1.0), dbplyr(v.2.0.0), MASS(v.7.3-53), rappdirs(v.0.3.1), boot(v.1.3-25), car(v.3.0-10), cli(v.2.2.0), gdata(v.2.18.0), quadprog(v.1.5-8), gower(v.0.2.2), parallel(v.4.0.3), igraph(v.1.2.6), pkgconfig(v.2.0.3), foreign(v.0.8-80), laeken(v.0.5.1), recipes(v.0.1.15), foreach(v.1.5.1), prodlim(v.2019.11.13), snakecase(v.0.11.0), callr(v.3.5.1), digest(v.0.6.27), rmarkdown(v.2.6), cellranger(v.1.1.0), leafsync(v.0.1.0), curl(v.4.3), gtools(v.3.8.2), urca(v.1.3-0), lifecycle(v.0.2.0), nlme(v.3.1-149), jsonlite(v.1.7.2), tseries(v.0.10-48), carData(v.3.0-4), desc(v.1.2.0), viridisLite(v.0.3.0), fansi(v.0.4.1), pillar(v.1.4.7), survival(v.3.2-7), httr(v.1.4.2), DEoptimR(v.1.0-8), pkgbuild(v.1.2.0), glue(v.1.4.2), xts(v.0.12.1), remotes(v.2.2.0), zip(v.2.1.1), iterators(v.1.0.13), png(v.0.1-7), leaflet.providers(v.1.9.0), class(v.7.3-17), stringi(v.1.5.3), stars(v.0.4-3), memoise(v.1.1.0) and e1071(v.1.7-4)

6.2 Appendix B: Baseline Multinomial Model

6.2.1 Model Building

df = readRDS('../Data/df.rds') # loading the data
df %<>% select(-fips) # removed since it was only used in the spatial model 
finalModel =  quiet(multinom(clustReLeveled ~ ., data = df)) # building the multinomial model

6.2.2 Resulting Model

# tabulating the model results as an HTML table, which we print below
stargazer(finalModel, type = 'html', p.auto = FALSE, out="../Data/multi.html", single.row = FALSE)
Dependent variable:
C2 C3 C4
(1) (2) (3)
countyTypeRural/Underserved -0.869*** 0.404*** -0.441***
(0.0004) (0.0004) (0.0005)
popDensity 0.0003** 0.0001 0.0001
(0.0001) (0.0001) (0.0001)
GovernmentResponseIndexMedian -0.028*** 0.039*** 0.051***
(0.007) (0.006) (0.005)
PercentSeniors -0.042*** 0.047*** 0.010
(0.012) (0.009) (0.009)
regionsB 72.713*** -0.029*** 0.426***
(0.0001) (0.0001) (0.0001)
regionsC 75.563*** 1.348*** 0.718***
(0.0002) (0.0001) (0.0001)
regionsD 72.547*** -1.807*** -0.717***
(0.0002) (0.0001) (0.0002)
regionsE -31.816 -3.853*** -2.897***
(0.0001) (0.001)
regionsF 71.946*** 0.056*** -1.272***
(0.0001) (0.0002) (0.0002)
regionsG 66.742*** -2.753*** -3.577***
(0.00002) (0.0003) (0.0002)
regionsH 66.344*** -2.093*** -3.236***
(0.00002) (0.0002) (0.0001)
regionsI 73.312*** 0.334*** -0.307***
(0.0001) (0.00005) (0.0001)
regionsJ 70.817*** -1.307*** -1.892***
(0.00004) (0.00004) (0.00003)
povertyPercent 0.065*** 0.051*** -0.013
(0.011) (0.010) (0.010)
Constant -70.024*** -3.016*** -0.269***
(0.0001) (0.0001) (0.00004)
Akaike Inf. Crit. 5,826.064 5,826.064 5,826.064
Note: p<0.1; p<0.05; p<0.01

6.2.3 Model’s Performance

# examining how well the model performed on our entire dataset
# Recall that we are fitting an explanatory model, and not a predictive model
predictedClass = predict(finalModel, df)
saveRDS(finalModel, '../Data/finalModel.rds') # saving the data

# Computing the Confusion Metrics and By Class Metrics
confMatrix = confusionMatrix(predictedClass, df$clustReLeveled)
saveRDS(confMatrix, '../Data/confMatrix.rds') # saving the data

# Printing the resulting tables nicely
pander(confMatrix$table)
  C1 C2 C3 C4
C1 865 7 125 260
C2 24 366 94 132
C3 57 50 177 98
C4 71 139 135 508
pander(confMatrix$byClass)
Table continues below
  Sensitivity Specificity Pos Pred Value Neg Pred Value
Class: C1 0.8505 0.8125 0.6881 0.9179
Class: C2 0.6512 0.9018 0.5942 0.9213
Class: C3 0.3333 0.9205 0.4634 0.8701
Class: C4 0.509 0.8365 0.5955 0.7827
Table continues below
  Precision Recall F1 Prevalence Detection Rate
Class: C1 0.6881 0.8505 0.7608 0.3272 0.2783
Class: C2 0.5942 0.6512 0.6214 0.1808 0.1178
Class: C3 0.4634 0.3333 0.3877 0.1708 0.05695
Class: C4 0.5955 0.509 0.5489 0.3211 0.1634
  Detection Prevalence Balanced Accuracy
Class: C1 0.4044 0.8315
Class: C2 0.1982 0.7765
Class: C3 0.1229 0.6269
Class: C4 0.2745 0.6728
pander(confMatrix$overall)
Table continues below
Accuracy Kappa AccuracyLower AccuracyUpper AccuracyNull
0.6165 0.4693 0.5991 0.6336 0.3272
AccuracyPValue McnemarPValue
1.667e-238 2.077e-32

6.2.4 Visualizing the Model’s Predictions

predictedProbs = fitted(finalModel) # computing predicted probabilities for each of the cluster outcome levels
mapResults = cbind(multiClassDF, predictedProbs) # col binding predProbs for Each Cluster with multiClassDF

# Finding indices to subset the data
numberOfClusters = unique(mapResults$cluster_group) %>% as.character() %>% length() 
startCol = ncol(mapResults) - numberOfClusters + 1
endCol = ncol(mapResults)

# Finding whether the predicted and actual clusters matched for each county
mapResults$LargestProbCluster = colnames(mapResults[, startCol:endCol])[apply(mapResults[, startCol:endCol], 1, which.max)] 
mapResults$match = ifelse(mapResults$cluster_group == mapResults$LargestProbCluster, 'Yes', 'No') %>% as.factor()

# Retrieving the U.S. county composite map as a simplefeature (since it has been overwritten)
cty_sf = counties_sf("aeqd") %>% filter(!state %in% c('Alaska', 'Hawaii')) # from albersusa
cty_sf %<>% geo_join(mapResults, by_sp= 'fips', by_df= 'fips')

# Creating a static visual for use in the paper
tiff(filename = '../Figures/clusterMatchMap.tiff', width = 1366, height =768, pointsize = 16)
tm_shape(cty_sf) + tm_polygons('match', title = 'Cluster Match', style = 'cont', palette = "div") +
  tm_layout(aes.palette = list(div = list("Yes" = "#CAB2D6", "No" = "#6A3D9A"))) +
  tm_credits(paste0('Based on Data from March 01, 2020 - ', endDatePrintV), position=c("right", "bottom"))
invisible( dev.off() ) # to suppress the unwanted output from dev.off


# Creating an interactive visual Using the Leaflet Package
#### Creating a longlat projection (required by leaflet)
leaflet_sf = counties_sf("longlat") %>% filter(!state %in% c('Alaska', 'Hawaii')) # from albersua
leaflet_sf %<>% geo_join(mapResults, by_sp= 'fips', by_df= 'fips')

#### Setting the Color Scheme
leafletPal =  colorFactor(palette = c("#CAB2D6", "#6A3D9A"), levels = c('Yes', 'No'), na.color = "white")

#### The visual
leaflet(height=500) %>% # initializing the leaflet map
  setView(lng = -96, lat = 37.8, zoom = 3.8) %>% # setting the view on Continental US
  addTiles() %>% # adding the default tiles
  addPolygons(data = leaflet_sf, stroke = FALSE, fillColor = ~leafletPal(leaflet_sf$match), # adding the data
              weight = 2, opacity = 1, color = "white", dashArray = "3", fillOpacity = 0.7, # adding color specs
              popup = paste("County:", leaflet_sf$name, '<br>', 
                            "Cluster #:", leaflet_sf$cluster_group, '<br>',
                            "Cluster Predicted:", leaflet_sf$LargestProbCluster, '<br>',
                            "Cluster Match:", leaflet_sf$match, '<br>')) %>% #pop-up Menu
  addLegend(position = "bottomleft", pal = leafletPal, values =  leaflet_sf$match, 
            title = "Cluster Match", opacity = 1) # legend formatting
LS0tDQp0aXRsZTogIkEgUmV0cm9zcGVjdGl2ZSBUd28tU3RhZ2UgQW5hbHlzaXMgb2YgQ09WSUQgQ2FzZXMgYnkgQ291bnR5Ig0KYXV0aG9yOg0KICAtIG5hbWU6ICJGYWRlbCBNLiBNZWdhaGVkIF5bRW1haWw6IGZtZWdhaGVkQG1pYW1pb2guZWR1IHwgUGhvbmU6ICsxLTUxMy01MjktNDE4NSB8IFdlYnNpdGU6IDxhIGhyZWY9XCJodHRwczovL21pYW1pb2guZWR1L2ZzYi9kaXJlY3RvcnkvP3VwPS9kaXJlY3RvcnkvbWVnYWhlZm1cIj5NaWFtaSBVbml2ZXJzaXR5IE9mZmljaWFsPC9hPl0iDQogICAgYWZmaWxpYXRpb246IEZhcm1lciBTY2hvb2wgb2YgQnVzaW5lc3MsIE1pYW1pIFVuaXZlcnNpdHkNCiAgLSBuYW1lOiAiQWxsaXNvbiBKb25lcy1GYXJtZXIgXltFbWFpbDogZmFybWVybDJAbWlhbWlvaC5lZHUgfCBQaG9uZTogKzEtNTEzLTUyOS00ODIzIHwgV2Vic2l0ZTogPGEgaHJlZj1cImh0dHBzOi8vbWlhbWlvaC5lZHUvZnNiL2RpcmVjdG9yeS8/dXA9L2RpcmVjdG9yeS9mYXJtZXJsMlwiPk1pYW1pIFVuaXZlcnNpdHkgT2ZmaWNpYWw8L2E+XSINCiAgICBhZmZpbGlhdGlvbjogRmFybWVyIFNjaG9vbCBvZiBCdXNpbmVzcywgTWlhbWkgVW5pdmVyc2l0eQ0KICAtIG5hbWU6ICJMb25nd2VuIFpoYW8gXltFbWFpbDogbG9uZ3dlbi56aGFvQHNsdS5lZHUgfCBXZWJzaXRlOiA8YSBocmVmPVwiaHR0cHM6Ly93d3cubGlua2VkaW4uY29tL2luL2xvbmd3ZW4temhhby0wNjkxNjQ4NlwiPkxpbmtlZEluIFNpdGU8L2E+XSINCiAgICBhZmZpbGlhdGlvbjogQ29sbGVnZSBvZiAgUHVibGljIEhlYWx0aCBhbmQgU29jaWFsIEp1c3RpY2UsIFNhaW50IExvdWlzIFVuaXZlcnNpdHkNCiAgLSBuYW1lOiAiU3RldmUgUmlnZG9uIF5bRW1haWw6IHN0ZXZlLnJpZ2RvbkBzbHUuZWR1IHwgV2Vic2l0ZTogPGEgaHJlZj1cImh0dHBzOi8vd3d3LnNsdS5lZHUvcHVibGljLWhlYWx0aC1zb2NpYWwtanVzdGljZS9mYWN1bHR5L3JpZ2Rvbi1zdGV2ZW4ucGhwXCI+U2FpbnQgTG91aXMgVW5pdmVyc2l0eSBPZmZpY2lhbDwvYT5dIg0KICAgIGFmZmlsaWF0aW9uOiBDb2xsZWdlIG9mICBQdWJsaWMgSGVhbHRoIGFuZCBTb2NpYWwgSnVzdGljZSwgU2FpbnQgTG91aXMgVW5pdmVyc2l0eQ0KYmlibGlvZ3JhcGh5OiBjb3ZpZFJlZnMuYmliDQpjc2w6IGFwYS5jc2wNCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVCICVkLCAlWScpYCINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQogICAgbnVtYmVyX3NlY3Rpb25zOiBUUlVFDQogICAgdGhlbWU6IHNpbXBsZXgNCiAgICBwYWdlZF9kZjogVFJVRQ0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KICAgIGNvZGVfZG93bmxvYWQ6IFRSVUUNCiAgaW5jbHVkZXM6DQogICAgaW5faGVhZGVyOiBzdHJ1Y3R1cmUudGV4DQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBjYWNoZSA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgcHJvZ3Jlc3MgPSBGQUxTRSwgDQogICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIGRwaSA9IDYwMCwNCiAgICAgICAgICAgICAgICAgICAgICBkZXYgPSBjKCdwbmcnLCAncG9zdHNjcmlwdCcpLA0KICAgICAgICAgICAgICAgICAgICAgIG91dC53aWR0aCA9ICcxMDAlJykNCm9wdGlvbnMocXdyYXBzMl9tYXJrdXAgPSAibWFya2Rvd24iKQ0KDQpsaWJyYXJ5KGdncGxvdDIpOyB0aGVtZV9zZXQodGhlbWVfYncoYmFzZV9zaXplID0gMTYsIGJhc2VfZmFtaWx5ID0gIkFyaWFsIikpICMgc2V0dGluZyB0aGUgcHJlZmVycmVkIGdncGxvdCB0aGVtZSB0byBidw0KbGlicmFyeShsZW1vbik7IGtuaXRfcHJpbnQudGJsID0gbGVtb25fcHJpbnQNCmBgYA0KDQojIFIgU2V0dXAgYW5kIFJlcXVpcmVkIFBhY2thZ2VzDQpJbiB0aGlzIHByb2plY3QsIHRoZSBvcGVuLXNvdXJjZSBSIHByb2dyYW1taW5nIGxhbmd1YWdlIGlzIHVzZWQgdG8gbW9kZWwgdGhlIHByb2dyZXNzaW9uIGluIHRoZSBDT1ZJRC0xOSBwYW5kZW1pYyBpbiBkaWZmZXJlbnQgVS5TLiBjb3VudGllcy4gUiBpcyBtYWludGFpbmVkIGJ5IGFuIGludGVybmF0aW9uYWwgdGVhbSBvZiBkZXZlbG9wZXJzIHdobyBtYWtlIHRoZSBsYW5ndWFnZSBhdmFpbGFibGUgYXQgW1RoZSBDb21wcmVoZW5zaXZlIFIgQXJjaGl2ZSBOZXR3b3JrXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy8pLiBSZWFkZXJzIGludGVyZXN0ZWQgaW4gcmV1c2luZyBvdXIgY29kZSBhbmQgcmVwcm9kdWNpbmcgb3VyIHJlc3VsdHMgc2hvdWxkIGhhdmUgUiBpbnN0YWxsZWQgbG9jYWxseSBvbiB0aGVpciBtYWNoaW5lcy4gUiBjYW4gYmUgaW5zdGFsbGVkIG9uIGEgbnVtYmVyIG9mIGRpZmZlcmVudCBvcGVyYXRpbmcgc3lzdGVtcyAoc2VlIFtXaW5kb3dzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy9iaW4vd2luZG93cy8pLCBbTWFjXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy9iaW4vbWFjb3N4LyksIGFuZCBbTGludXhdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL2Jpbi9saW51eC8pIGZvciB0aGUgaW5zdGFsbGF0aW9uIGluc3RydWN0aW9ucyBmb3IgdGhlc2Ugc3lzdGVtcykuIFdlIGFsc28gcmVjb21tZW5kIHVzaW5nIHRoZSBSU3R1ZGlvIGludGVyZmFjZSBmb3IgUi4gVGhlIHJlYWRlciBjYW4gW2Rvd25sb2FkIFJTdHVkaW9dKGh0dHA6Ly93d3cucnN0dWRpby5jb20vaWRlKSBmb3IgZnJlZSBieSBmb2xsb3dpbmcgdGhlIGluc3RydWN0aW9ucyBhdCB0aGUgbGluay4gRm9yIG5vbi1SIHVzZXJzLCB3ZSByZWNvbW1lbmQgdGhlIFtIYW5kcy1vbiBQcm9ncmFtbWluZyB3aXRoIFJdKGh0dHBzOi8vcnN0dWRpby1lZHVjYXRpb24uZ2l0aHViLmlvL2hvcHIvcGFja2FnZXMuaHRtbCkgZm9yIGEgYnJpZWYgb3ZlcnZpZXcgb2YgdGhlIHNvZnR3YXJlJ3MgZnVuY3Rpb25hbGl0eS4gSGVyZWFmdGVyLCB3ZSBhc3N1bWUgdGhhdCB0aGUgcmVhZGVyIGhhcyBhbiBpbnRyb2R1Y3RvcnkgdW5kZXJzdGFuZGluZyBvZiB0aGUgUiBwcm9ncmFtbWluZyBsYW5ndWFnZS4NCg0KSW4gdGhlIGNvZGUgY2h1bmsgYmVsb3csIHdlIGxvYWQgdGhlIHBhY2thZ2VzIHVzZWQgdG8gc3VwcG9ydCBvdXIgYW5hbHlzaXMuIE5vdGUgdGhhdCB0aGUgY29kZSBvZiB0aGlzIGFuZCBhbnkgb2YgdGhlIGNvZGUgY2h1bmtzIGNhbiBiZSBoaWRkZW4gYnkgY2xpY2tpbmcgb24gdGhlICdIaWRlJyBidXR0b24gdG8gZmFjaWxpdGF0ZSB0aGUgbmF2aWdhdGlvbi4gKipUaGUgcmVhZGVyIGNhbiBoaWRlIGFsbCBjb2RlIGFuZC9vciBkb3dubG9hZCB0aGUgUm1kIGZpbGUgYXNzb2NpYXRlZCB3aXRoIHRoaXMgZG9jdW1lbnQgYnkgY2xpY2tpbmcgb24gdGhlIENvZGUgYnV0dG9uIG9uIHRoZSB0b3AgcmlnaHQgY29ybmVyIG9mIHRoaXMgZG9jdW1lbnQuKiogT3VyIGlucHV0IGFuZCBvdXRwdXQgZmlsZXMgY2FuIGFsc28gYmUgYWNjZXNzZWQvIGRvd25sb2FkZWQgZnJvbSBbZm1lZ2FoZWQvY292aWQxOV0oaHR0cHM6Ly9naXRodWIuY29tL2ZtZWdhaGVkL2NvdmlkMTkpLiANCg0KYGBge3IgcGFja2FnZXMsIGNhY2hlPUZBTFNFfQ0KaWYocmVxdWlyZShwYWNtYW4pPT1GQUxTRSkgaW5zdGFsbC5wYWNrYWdlcygicGFjbWFuIikgIyBjaGVjayB0byBzZWUgaWYgdGhlIHBhY21hbiBwYWNrYWdlIGlzIGluc3RhbGxlZDsgaWYgbm90IGluc3RhbGwgaXQNCmlmKHJlcXVpcmUoZGV2dG9vbHMpPT1GQUxTRSkgaW5zdGFsbC5wYWNrYWdlcygiZGV2dG9vbHMiKSAjIGNoZWNrIHRvIHNlZSBpZiB0aGUgZGV2dG9vbHMgcGFja2FnZSBpcyBpbnN0YWxsZWQ7IGlmIG5vdCBpbnN0YWxsIGl0DQoNCiMgdG8gY2hlY2sgYW5kIGluc3RhbGwgaWYgdGhlc2UgcGFja2FnZXMgYXJlIG5vdCBmb3VuZCBsb2NhbGx5IG9uIG1hY2hpbmUNCmlmKHJlcXVpcmUoYWxiZXJzdXNhKT09RkFMU0UpIGRldnRvb2xzOjppbnN0YWxsX2dpdGh1YignaHJicm1zdHIvYWxiZXJzdXNhJykgI2luc3RhbGwgcGFja2FnZSBpZiBuZWVkZWQNCmlmKHJlcXVpcmUoYWxiZXJzdXNhKT09RkFMU0UpIGRldnRvb2xzOjppbnN0YWxsX2dpdGh1YignZHJlYW1Scy9yMmQzbWFwcycpICNpbnN0YWxsIHBhY2thZ2UgaWYgbmVlZGVkDQoNCg0KIyBjaGVjayBpZiBwYWNrYWdlcyBhcmUgbm90IGluc3RhbGxlZDsgaWYgeWVzLCBpbnN0YWxsIG1pc3NpbmcgcGFja2FnZXMNCnBhY21hbjo6cF9sb2FkKHRpZHl2ZXJzZSwgbWFncml0dHIsIGphbml0b3IsIGRhdGFQcmVwYXJhdGlvbiwgbHVicmlkYXRlLCBza2ltciwgIyBmb3IgZGF0YSBhbmFseXNpcw0KICAgICAgICAgICAgICAgQ09WSUQxOSwgcnZlc3QsIHJlYWR4bCwgIyBmb3IgZXh0cmFjdGluZyByZWxldmFudCBkYXRhDQogICAgICAgICAgICAgICBEVCwgcGFuZGVyLCBzdGFyZ2F6ZXIsIGtuaXRyLCAjIGZvciBmb3JtYXR0aW5nIGFuZCBuaWNlbHkgcHJpbnRlZCBvdXRwdXRzDQogICAgICAgICAgICAgICBzY2FsZXMsIFJDb2xvckJyZXdlciwgRGF0YUV4cGxvcmVyLCB0aWZmLCBncmlkLCMgZm9yIHBsb3RzDQogICAgICAgICAgICAgICBwbG90bHksIGFsYmVyc3VzYSwgdGlncmlzLCBsZWFmbGV0LCB0bWFwLCAjIGZvciBtYXBzDQogICAgICAgICAgICAgICB6b28sIGZwcDIsIE5iQ2x1c3QsICMgZm9yIFRTIGFuYWx5c2lzIGFuZCBjbHVzdGVyaW5nDQogICAgICAgICAgICAgICBWSU0sIHJnZGFsLCAgc3BkZXAsICBuaW1ibGUsIGZhc3REdW1taWVzLCBtYXRyaXhTdGF0cywgIyBmb3Igc3BhdGlhbCByZWdyZXNzaW9uIA0KICAgICAgICAgICAgICAgbm5ldCwgY2FyZXQsICMgZm9yIG11bHRpbm9taWFsIHJlZ3Jlc3Npb24gbW9kZWxpbmcNCiAgICAgICAgICAgICAgIGNvbmZsaWN0ZWQpICMgZm9yIG1hbmFnaW5nIGNvbmZsaWN0cyBpbiBmdW5jdGlvbnMgd2l0aCBzYW1lIG5hbWVzDQoNCiMgSGFuZGxpbmcgY29uZmxpY3RpbmcgZnVuY3Rpb24gbmFtZXMgZnJvbSBwYWNrYWdlcw0KY29uZmxpY3RfcHJlZmVyKCdjb21iaW5lJywgJ2RwbHlyJykgIyBQcmVmZXJyaW5nIGRwbHlyOjpjb21iaW5lIG92ZXIgYW55IG90aGVyIHBhY2thZ2UNCmNvbmZsaWN0X3ByZWZlcignc2VsZWN0JywgImRwbHlyIikgI1ByZWZlcnJpbmcgZHBseXI6OnNlbGVjdCBvdmVyIGFueSBvdGhlciBwYWNrYWdlDQpjb25mbGljdF9wcmVmZXIoInN1bW1hcml6ZSIsICJkcGx5ciIpICMgc2ltaWxhciB0byBhYm92ZSBidXQgd2l0aCBkcGx5cjo6c3VtbWFyaXplDQpjb25mbGljdF9wcmVmZXIoImZpbHRlciIsICJkcGx5ciIpICMgUHJlZmVycmluZyBmaWx0ZXIgZnJvbSBkcGx5cg0KY29uZmxpY3RfcHJlZmVyKCJkaXN0IiwgInN0YXRzIikgIyBQcmVmZXJyaW5nIGRpc3QgZnJvbSBzdGF0cw0KY29uZmxpY3RfcHJlZmVyKCJhcy5kaXN0IiwgInN0YXRzIikgIyBQcmVmZXJyaW5nIGFzLmRpc3QgZnJvbSBzdGF0cw0KDQojIEN1c3RvbSBGdW5jdGlvbnMNCnNvdXJjZV91cmwoJ2h0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9mbWVnYWhlZC9jb3ZpZDE5LWRlYXRocy9tYXN0ZXIvTWFya2Rvd24vY3VzdG9tX2Z1bmN0aW9ucy5SJykNCg0Kc2V0LnNlZWQoMjAyMCkgIyB0byBhc3Npc3Qgd2l0aCByZXByb2R1Y2liaWxpdHkNCnNJbmZvID0gc2Vzc2lvbkluZm8oKSAjIHNhdmluZyBhbGwgdGhlIHBhY2thZ2VzL2Z1bmN0aW9ucyBhbmQgc2Vzc2lvbiBpbmZvDQpgYGANCg0KIyBFeHRyYWN0aW5nIHRoZSBEYXRhc2V0cw0KDQpGb3Igb3VyIGFuYWx5c2lzLCB3ZSBmdXNlIGRhdGEgZnJvbSBtdWx0aXBsZSBzb3VyY2VzLiBXZSBkZXNjcmliZSB0aGUgcHJvY2VzcyBvZiBvYnRhaW5pbmcgYW5kIG1lcmdpbmcgZWFjaCBvZiB0aGVzZSBzb3VyY2VzIGluIHRoZSBzdWJzZWN0aW9ucyBiZWxvdy4NCg0KDQojIyBUaW1lIFNlcmllcyBEYXRhDQpJbiB0aGlzIHNlY3Rpb24sIHdlIHV0aWxpemUgdGhlIFtDT1ZJRDE5IHBhY2thZ2VdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9DT1ZJRDE5L0NPVklEMTkucGRmKSB0byBvYnRhaW4gdGhlIGZvbGxvd2luZyBpbmZvcm1hdGlvbjogW0BHdWlkb3R0aTIwMjBdICAgIA0KDQogIC0gKipDb25maXJtZWQgY2FzZXMsIHJlY292ZXJpZXMgYW5kIGRlYXRocyoqOyAgICANCiAgLSAqKnBvbGljeSBpbmZvcm1hdGlvbioqIChlLmcuLCB0cmFuc3BvcnQgY2xvc2luZywgc2Nob29sIGNsb3NpbmcsIGNsb3NpbmcgZXZlbnQsIG1vdmVtZW50IHJlc3RyaWN0aW9ucywgdGVzdGluZyBwb2xpY2llcywgYW5kIGNvbnRhY3QgdHJhY2luZyk7ICBhbmQNCiAgLSAqKlBvcHVsYXRpb24gYW5kIHN0YW5kYXJkIGdlb2dyYXBoaWMgaW5mb3JtYXRpb24qKiBmb3IgZWFjaCBjb3VudHkuIA0KDQpGcm9tIHRoaXMgaW5mb3JtYXRpb24sIHdlIGhhdmUgYWxzbyBjb21wdXRlZCB0aGUgbmV3IGRhaWx5IGFuZCB3ZWVrbHkgY29uZmlybWVkIGNhc2VzL2RlYXRocyBwZXIgY291bnR5LiBUaGUgZGF0YSBpcyBzdG9yZWQgaW4gYSB0aWR5IGZvcm1hdCwgYnV0IGNhbiBiZSBleHBhbmRlZCB0byBhIHdpZGUgZm9ybWF0IHVzaW5nIGBwaXZvdF93aWRlcigpYCBmcm9tIHRoZSBbdGlkeXZlcnNlXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnLykgcGFja2FnZS4NCg0KYGBge3IgY29uZmlybWVkQ2FzZXMsIHJlc3VsdHM9J2FzaXMnfQ0KZW5kRGF0ZSA9ICcyMDIxLTAxLTAyJw0KZW5kRGF0ZVByaW50ViA9IGZvcm1hdCh5bWQoZW5kRGF0ZSksIGZvcm1hdCA9ICIlYiAlZCwgJVkiKQ0KDQpjb3VudGllcyA9IGNvdmlkMTkoY291bnRyeSA9ICJVUyIsIA0KICAgICAgICAgICAgICAgICAgIGxldmVsID0gMywgIyBmb3IgY291bnR5DQogICAgICAgICAgICAgICAgICAgc3RhcnQgPSAiMjAyMC0wMy0wMSIsICMgRmlyc3QgU3VuZGF5IGluIE1hcmNoDQogICAgICAgICAgICAgICAgICAgZW5kID0gZW5kRGF0ZSwgIyBlbmQgRGF0ZSANCiAgICAgICAgICAgICAgICAgICByYXcgPSBGQUxTRSwgIyB0byBlbnN1cmUgdGhhdCBhbGwgY291bnRpZXMgaGF2ZSB0aGUgc2FtZSBncmlkIG9mIGRhdGVzDQogICAgICAgICAgICAgICAgICAgYW1yID0gTlVMTCwgIyB3ZSBhcmUgbm90IHVzaW5nIHRoZSBhcHBsZSBtb2JpbGl0eSBkYXRhIGZvciBvdXIgYW5hbHlzaXMNCiAgICAgICAgICAgICAgICAgICBnbXIgPSBOVUxMLCAjIHdlIGFyZSBub3QgdXNpbmcgdGhlIEdvb2dsZSBtb2JpbGl0eSBkYXRhIGZvciBvdXIgYW5hbHlzaXMNCiAgICAgICAgICAgICAgICAgICB3YiA9IE5VTEwsICMgd29ybGQgYmFuayBkYXRhIG5vdCBoZWxwZnVsIGZvciBjb3VudHkgbGV2ZWwgYW5hbHlzaXMNCiAgICAgICAgICAgICAgICAgICB2ZXJib3NlID0gRkFMU0UpDQoNCmNvdW50aWVzICU8PiUgIyBuZXh0IGxpbmUgcmVtb3ZlcyBub24tY29udGlndW91cyBVUyBzdGF0ZXMvdGVycml0b3JpZXMNCiAgZmlsdGVyKCFhZG1pbmlzdHJhdGl2ZV9hcmVhX2xldmVsXzIgJWluJSBjKCdBbGFza2EnLCAnSGF3YWlpJywgJ1B1ZXJ0byBSaWNvJywgJ05vcnRoZXJuIE1hcmlhbmEgSXNsYW5kcycsICdWaXJnaW4gSXNsYW5kcycpKSAlPiUgDQogIGZhc3RfZmlsdGVyX3ZhcmlhYmxlcyh2ZXJib3NlID0gRkFMU0UpICU+JSAjZHJvcHBpbmcgaW52YXJpYW50IGNvbHVtbnMgb3IgYmlqZWN0aW9ucw0KICBmaWx0ZXIoIWlzLm5hKGtleV9udW1lcmljKSkgJT4lICAjIHRoZXNlIGFyZSBub3QgY291bnRpZXMNCiAgZ3JvdXBfYnkoaWQpICU+JSAjIGdyb3VwaW5nIHRoZSBkYXRhIGJ5IHRoZSBpZCBjb2x1bW4gdG8gbWFrZSBjb21wdXRhdGlvbnMgY29ycmVjdA0KICBhcnJhbmdlKGlkLCBkYXRlKSAlPiUgIyB0byBlbnN1cmUgY29ycmVjdCBjYWxjdWxhdGlvbnMNCiAgbXV0YXRlKGRheSA9IHdkYXkoZGF0ZSwgbGFiZWwgPSBUUlVFKSAlPiUgZmFjdG9yKG9yZGVyZWQgPSBGKSwgIyBkYXkgb2Ygd2Vlaw0KICAgICAgICAgbmV3Q2FzZXMgPSBjKE5BLCBkaWZmKGNvbmZpcm1lZCkpLCAjIGNvbXB1dGluZyBuZXcgZGFpbHkgY2FzZXMgcGVyIGNvdW50eQ0KICAgICAgICAgbmV3RGVhdGhzID0gYyhOQSwgZGlmZihkZWF0aHMpKSApICAjIGNvbXB1dGluZyBuZXcgZGFpbHkgZGVhdGhzIHBlciBjb3VudHkNCg0KIyBtYW51YWxseSBpZGVudGlmeWluZyBmYWN0b3IgdmFyaWFibGVzDQpmYWN0b3JWYXJzID0gYygic2Nob29sX2Nsb3NpbmciLCAid29ya3BsYWNlX2Nsb3NpbmciLCAiY2FuY2VsX2V2ZW50cyIsDQogICAgICAgICAgICAgICAiZ2F0aGVyaW5nc19yZXN0cmljdGlvbnMiLCAidHJhbnNwb3J0X2Nsb3NpbmciLCAic3RheV9ob21lX3Jlc3RyaWN0aW9ucyIsDQogICAgICAgICAgICAgICAiaW50ZXJuYWxfbW92ZW1lbnRfcmVzdHJpY3Rpb25zIiwgImludGVybmF0aW9uYWxfbW92ZW1lbnRfcmVzdHJpY3Rpb25zIiwNCiAgICAgICAgICAgICAgICJpbmZvcm1hdGlvbl9jYW1wYWlnbnMiLCAidGVzdGluZ19wb2xpY3kiLCAiY29udGFjdF90cmFjaW5nIikNCg0KY291bnRpZXMgJTw+JSAjIGNvbnZlcnRpbmcgdGhvc2UgdmFyaWFibGVzIGludG8gY2hhcmFjdGVyIGFuZCB0aGVuIGZhY3Rvcg0KICBtdXRhdGVfYXQoLnZhcnMgPSB2YXJzKGFueV9vZihmYWN0b3JWYXJzKSksIC5mdW5zID0gYXMuY2hhcmFjdGVyKSAlPiUgDQogIG11dGF0ZV9hdCgudmFycyA9IHZhcnMoYW55X29mKGZhY3RvclZhcnMpKSwgLmZ1bnMgPSBhcy5mYWN0b3IpDQoNCiMgU2F2aW5nIHRoZSBkYXRhIGludG8gYW4gUkRTIGZpbGUNCnNhdmVSRFMoY291bnRpZXMsIHBhc3RlMCgiLi4vRGF0YS9jb3VudGllcy5yZHMiKSkNCmBgYA0KDQojIyBDcm9zcyBTZWN0aW9uYWwgRGF0YQ0KDQpJbiB0aGUgY29kZSBjaHVuayBiZWxvdywgd2Ugb2J0YWluIHNldmVuIGFkZGl0aW9uYWwgZGF0YXNldHMsIHdob3NlIHZhcmlhYmxlcyBjYW4gZXhwbGFpbiB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGUgdGltZS1zZXJpZXMgb2YgdGhlIG51bWJlciBvZiBDT1ZJRCBjYXNlcyBwZXIgY291bnR5OiAgDQoNCiAgQS4gKlJ1cmFsLyBVbmRlcnNlcnZlZCBDb3VudGllczoqIEZyb20gdGhlIFtDb25zdW1lciBGaW5hbmNpYWwgUHJvdGVjdGlvbiBCdXJlYXVdKGh0dHBzOi8vd3d3LmNvbnN1bWVyZmluYW5jZS5nb3YvcG9saWN5LWNvbXBsaWFuY2UvZ3VpZGFuY2UvbW9ydGdhZ2UtcmVzb3VyY2VzL3J1cmFsLWFuZC11bmRlcnNlcnZlZC1jb3VudGllcy1saXN0LyksIHdlIGhhdmUgb2J0YWluZWQgdGhlIEZpbmFsIDIwMjAgTGlzdCB0aXRsZWQ6ICpSdXJhbCBvciB1bmRlcnNlcnZlZCBjb3VudGllcyouIFBlciB0aGUgd2Vic2l0ZSwgdGhlIHByb2NlZHVyZSBmb3IgZGV0ZXJtaW5pbmcgdGhlIGNsYXNzaWZpY2F0aW9uIG9mIGEgY291bnR5IGlzIGFzIGZvbGxvd3M6ICJCZWdpbm5pbmcgaW4gMjAyMCwgdGhlIHJ1cmFsIG9yIHVuZGVyc2VydmVkIGNvdW50aWVzIGxpc3RzIHVzZSBhIG1ldGhvZG9sb2d5IGZvciBpZGVudGlmeWluZyB1bmRlcnNlcnZlZCBjb3VudGllcyBkZXNjcmliZWQgaW4gdGhlIEJ1cmVhdeKAmXMgaW50ZXJwcmV0aXZlIHJ1bGU6IFRydXRoIGluIExlbmRpbmcgQWN0IChSZWd1bGF0aW9uIFopOyBbRGV0ZXJtaW5pbmcg4oCcVW5kZXJzZXJ2ZWTigJ0gQXJlYXMgVXNpbmcgSG9tZSBNb3J0Z2FnZSBEaXNjbG9zdXJlIEFjdCBEYXRhXShodHRwczovL3d3dy5jb25zdW1lcmZpbmFuY2UuZ292L3BvbGljeS1jb21wbGlhbmNlL3J1bGVtYWtpbmcvZmluYWwtcnVsZXMvdHJ1dGgtbGVuZGluZy1yZWd1bGF0aW9uLXotdW5kZXJzZXJ2ZWQtYXJlYXMtaG9tZS1tb3J0Z2FnZS1kaXNjbG9zdXJlLWFjdC1kYXRhLykuIiAgIA0KDQogIEIuIEJhc2VkIG9uIHRoZSBbVVMgQ2Vuc3VzIERhdGFdKGh0dHBzOi8vd3d3LmNlbnN1cy5nb3YvbGlicmFyeS9wdWJsaWNhdGlvbnMvMjAxMS9jb21wZW5kaWEvdXNhLWNvdW50aWVzLTIwMTEuaHRtbCksIHdlIGV4dHJhY3RlZCB0aGUgIGxhbmQgYXJlYSBpbiBzcXVhcmUgbWlsZXMgZm9yIGVhY2ggY291bnR5LCB3aGljaCB3ZSBjb21iaW5lZCB3aXRoIHBvcHVsYXRpb24gdG8gY29tcHV0ZSAqKmVhY2ggY291bnR5J3MgcG9wdWxhdGlvbiBkZW5zaXR5KiosIHdoaWNoIHdlIGh5cG90aGVzaXplIHRvIGJlIHByZWRpY3RpdmUgb2YgaG90c3BvdHMgZm9yIENPVklEIHRyYW5zbWlzc2lvbiBiYXNlZCBvbiB0aGUgYXZhaWxhYmxlIENPVklELTE5IGxpdGVyYXR1cmUuICANCiAgDQogIEMuIEJhc2VkIG9uIHRoZSBARFZOL1ZPUUNIUV8yMDE4LCB3ZSBoYXZlIG9idGFpbmVkIHRoZSB2b3RpbmcgcmVzdWx0cyBmb3IgYWxsIGNvdW50aWVzIGluIHRoZSAyMDE2IFByZXNpZGVudGlhbCBlbGVjdGlvbnMuIFRoZSBkYXRhIHdhcyB1c2VkIHRvIGNvbXB1dGUgdGhlIHBlcmNlbnRhZ2Ugb2YgdG90YWwgdm90ZXMgdGhhdCB3ZW50IHRvIFByZXNpZGVudCBUcnVtcCwgd2l0aCB0aGUgdW5kZXJseWluZyBoeXBvdGhlc2lzIHRoYXQgdGhlIHBvbGl0aWNpemF0aW9uIG9mIENPVklEIHJlc3BvbnNlIChlLmcuLCBwZXJjZXB0aW9uL3dpbGxpbmduZXNzIHRvIHVzZSBmYWNlIG1hc2tzLCBwb2xpY2llcyBhbmQgdGhlIHBvcHVsYXRpb27igJlzIHJlYWN0aW9uIHRvIHRoZSBkaXNlYXNlKSBtYXkgYmUgZXhwbGFpbmVkIGJ5IHBhcnR5IGFmZmlsaWF0aW9uLg0KICANCiAgRC4gV2UgZXh0cmFjdGVkICoqYW4gb3ZlcmFsbCBnb3Zlcm5tZW50IHJlc3BvbnNlIGluZGV4IGNhcHR1cmluZyB0aGUgc3RyZW5ndGggb2YgQ09WSUQtMTkgcmVzcG9uc2UgcG9saWNpZXMgb24gYSBzdGF0ZSAoYW5kIHRoZSBEaXN0cmljdCBvZiBDb2x1bWJpYSkgbGV2ZWwqKiBmcm9tIHRoZSBbQmxhdmF0bmlrIFNjaG9vbCBvZiBHb3Zlcm5tZW50J3MgR2l0SHViIFJlcG9zaXRvcnldKGh0dHBzOi8vZ2l0aHViLmNvbS9PeENHUlQvVVNBLWNvdmlkLXBvbGljeSkuIFRoaXMgaW5kZXggY2FwdHVyZXMgMTMgZGlmZmVyZW50IGluZGljYXRvcnMsIGNhcHR1cmluZyB0aGUgYGBmdWxsIHJhbmdlIG9mIGdvdmVybm1lbnQgcmVzcG9uc2UnJy4gRGV0YWlscyBmb3IgaG93IHRoaXMgaW5kaWNhdG9yIGlzIGNvbXB1dGVkIGNhbiBiZSBmb3VuZCBhdCBbQlNHLVdQLTIwMjAvMDM0XShodHRwczovL3d3dy5ic2cub3guYWMudWsvc2l0ZXMvZGVmYXVsdC9maWxlcy8yMDIwLTA4L0JTRy1XUC0yMDIwLTAzNC5wZGYpLiAgDQogIA0KICBFLiBCYXNlZCBvbiB0aGUgW2ZvbGxvd2luZyBLYWlzZXIgSGVhbHRoIE5ld3MgV2VicGFnZV0oaHR0cHM6Ly9raG4ub3JnL25ld3MvYXMtY29yb25hdmlydXMtc3ByZWFkcy13aWRlbHktbWlsbGlvbnMtb2Ytb2xkZXItYW1lcmljYW5zLWxpdmUtaW4tY291bnRpZXMtd2l0aC1uby1pY3UtYmVkcy8jbG9va3VwKSwgd2UgZXh0cmFjdGVkIGJ5IGNvdW50eSBpbmZvcm1hdGlvbiBvbiB0aGUgKipwZXJjZW50IG9mIHBvcHVsYXRpb24gYWdlZCA2MCsqKiBhbmQgdGhlICoqbnVtYmVyIG9mIElDVSBiZWRzIHBlciBTZW5pb3JzKiouICANCiAgDQogIEYuIFdlIGhhdmUgZW5naW5lZXJlZCBhIGByZWdpb25gIHZhcmlhYmxlIGJhc2VkIG9uIHRoZSBbQ0RDJ3MgMTAgUmVnaW9ucyBGcmFtZXdvcmtdKGh0dHBzOi8vd3d3LmNkYy5nb3YvY29vcmRpbmF0ZWRjaHJvbmljL2RvY3MvbmNjZHBocC1yZWdpb25zLW1hcC5wZGYpLiBXaGlsZSBnZW9ncmFwaGljIHJlZ2lvbnMgYXJlIGh5cG90aGVzaXplZCB0byBiZSBhIGZhY3RvciBpbiBkaXNlYXNlIG91dGJyZWFrcywgd2UgY2hvc2UgdG8gdXRpbGl6ZSB0aGUgQ0RDIHJlZ2lvbnMgc3BlY2lmaWNhbGx5IGJhc2VkIG9uIHRoZSBmb2xsb3dpbmcgZXhwbGFuYXRpb24gZnJvbSB0aGUgYWZvcmVtZW50aW9uZWQgbGluazogIA0KICA+ICJDREPigJlzIE5hdGlvbmFsIENlbnRlciBmb3IgQ2hyb25pYyBEaXNlYXNlIFByZXZlbnRpb24gYW5kIEhlYWx0aCBQcm9tb3Rpb24gKE5DQ0RQSFApIGlzIHN0cmVuZ3RoZW5pbmcgdGhlIGNvbnNpc3RlbmN5IGFuZCBxdWFsaXR5IG9mIHRoZSBndWlkYW5jZSwgY29tbXVuaWNhdGlvbnMsIGFuZCB0ZWNobmljYWwgYXNzaXN0YW5jZSBwcm92aWRlZCB0byBzdGF0ZXMgdG8gaW1wcm92ZSBjb29yZGluYXRpb24gYWNyb3NzIG91ciBzdGF0ZSBwcm9ncmFtcyINCg0KICBHLiBCYXNlZCBvbiB0aGUgW0NlbnN1cydzIFNtYWxsIEFyZWEgSW5jb21lIGFuZCBQb3ZlcnR5IEVzdGltYXRlcyAoU0FJUEUpIFByb2dyYW1dKGh0dHBzOi8vd3d3LmNlbnN1cy5nb3YvcHJvZ3JhbXMtc3VydmV5cy9zYWlwZS5odG1sKSwgd2UgZXh0cmFjdGVkIHRoZSBlc3RpbWF0ZSBmb3IgdGhlICoqcGVyY2VudCBvZiBwb3B1bGF0aW9uIGluIHBvdmVydHkqKi4gVGhlIGVzdGltYXRlIGlzIGJhc2VkIG9uIDIwMTggZGF0YSAocmVsZWFzZWQgaW4gRGVjZW1iZXIgMjAxOSkuIEF0IHRoZSB0aW1lIG9mIHRoZSBzdGFydCBvZiBvdXIgYW5hbHlzaXMsIHRoZXNlIGVzdGltYXRlcyB3ZXJlIHRoZSBtb3N0IHVwIHRvIGRhdGUgcHVibGljbHkgYXZhaWxhYmxlIGRhdGEuDQoNCg0KYGBge3IgcG9zc2libGVYdmFyc30NCmNyb3NzU2VjdGlvbmFsRGF0YSA9IGNvdW50aWVzICU+JSB1bmdyb3VwKCkgJT4lIA0KICBzZWxlY3QoaWQsIGtleV9udW1lcmljLCBrZXlfZ29vZ2xlX21vYmlsaXR5LCBwb3B1bGF0aW9uLA0KICAgICAgICAgYWRtaW5pc3RyYXRpdmVfYXJlYV9sZXZlbF8yLCBhZG1pbmlzdHJhdGl2ZV9hcmVhX2xldmVsXzMpICU+JQ0KICB1bmlxdWUoKQ0KDQojIFtBXSBSdXJhbCBvciBVcmJhbiBDbGFzc2lmaWNhdGlvbiBvZiB0aGUgQ291bnR5DQpydSA9IHJlYWQuY3N2KCJodHRwczovL3d3dy5jb25zdW1lcmZpbmFuY2UuZ292L2RvY3VtZW50cy84OTExL2NmcGJfcnVyYWwtdW5kZXJzZXJ2ZWQtbGlzdF8yMDIwLmNzdiIpDQpydSAlPD4lICB0cmFuc211dGUoa2V5X251bWVyaWMgPSBGSVBTLkNvZGUsICNyZW5hbWluZyBGSVBTLkNvZGUgdG8ga2V5X251bWVyaWMgDQogICAgICAgICAgICAgICAgY291bnR5VHlwZSA9ICJSdXJhbC9VbmRlcnNlcnZlZCIpICMgY3JlYXRlcyB0d28gdmFycyBhbmQgZHJvcCBvbGQgdmFycw0KY3Jvc3NTZWN0aW9uYWxEYXRhID0gbWVyZ2UoY3Jvc3NTZWN0aW9uYWxEYXRhLCBydSwgYnkgPSAia2V5X251bWVyaWMiLCBhbGwueCA9IFRSVUUpICMgdG8gZGVmaW5lIE5BIGNvdW50aWVzDQpjcm9zc1NlY3Rpb25hbERhdGEkY291bnR5VHlwZSAlPD4lIHJlcGxhY2VfbmEoIk90aGVyIikgIyBmb3IgYW55IGNvdW50eSBub3QgaW4gdGhlIENvbnN1bWVyIEZJTiBkYXRhIHJlcGxhY2UgTkEgYnkgT3RoZXINCg0KDQojIFtCXSBQb3B1bGF0aW9uIERlbnNpdHkgb2YgRWFjaCBDb3VudHkNCmRvd25sb2FkLmZpbGUoImh0dHBzOi8vd3d3Mi5jZW5zdXMuZ292L2xpYnJhcnkvcHVibGljYXRpb25zLzIwMTEvY29tcGVuZGlhL3VzYS1jb3VudGllcy9leGNlbC9MTkQwMS54bHMiLA0KICAgICAgICAgICAgICBkZXN0ZmlsZSA9ICIuLi9EYXRhL0xORDAxLnhscyIsIG1vZGUgPSAid2IiKSAjIGRvd25sb2FkaW5nIExhbmQgQXJlYSBEYXRhIFBlciB0aGUgMjAxMCBDZW5zdXMNCmFyZWFzID0gcmVhZF9leGNlbCgiLi4vRGF0YS9MTkQwMS54bHMiKSAlPiUgIyByZWFkaW5nIHRoZSBFeGNlbCBmaWxlDQogIHNlbGVjdChTVENPVSwgTE5EMTEwMjEwRCkgI3NlbGVjdGluZyBvbmx5IHRoZSBGSVBTIGFuZCB0aGUgTGFuZCBBcmVhIGZyb20gdGhlIDIwMTAgQ2Vuc3VzIHZhcmlhYmxlcw0KY29sbmFtZXMoYXJlYXMpID0gYygia2V5X251bWVyaWMiLCAiTGFuZEFyZWFTcU1pbGVzMjAxMCIpICMgUmVuYW1pbmcgdGhlIGNvbHVtbnMNCmFyZWFzJGtleV9udW1lcmljICU8PiUgYXMubnVtZXJpYygpICMgdG8gcmVtb3ZlIGxlYWRpbmcgMCANCg0KY3Jvc3NTZWN0aW9uYWxEYXRhID0gbWVyZ2UoY3Jvc3NTZWN0aW9uYWxEYXRhLCBhcmVhcywgYnkgPSJrZXlfbnVtZXJpYyIsIGFsbC54ID0gVFJVRSkgIyBhZGRpbmcgdGhlIGFyZWEgdG8gZGF0YSBmcmFtZQ0KY3Jvc3NTZWN0aW9uYWxEYXRhJHBvcERlbnNpdHkgPSBjcm9zc1NlY3Rpb25hbERhdGEkcG9wdWxhdGlvbiAvIGNyb3NzU2VjdGlvbmFsRGF0YSRMYW5kQXJlYVNxTWlsZXMyMDEwICMgY3JlYXRpbmcgdGhlIHBvcHVsYXRpb24gZGVuc2l0eSB2YXJpYWJsZQ0KY3Jvc3NTZWN0aW9uYWxEYXRhICU8PiUgc2VsZWN0KC1jKHBvcHVsYXRpb24sIExhbmRBcmVhU3FNaWxlczIwMTApKSAjZHJvcHBpbmcgdHdvIHZhcmlhYmxlcyB1c2VkIGluIGNyZWF0aW5nIHBvcCBkZW5zaXR5IA0KDQoNCiMgW0NdIDIwMTYgUHJlc2lkZW50aWFsIEVsZWN0aW9ucyBDb3VudHkgRGF0YSBmcm9tIEhhcnZhcmQgaHR0cHM6Ly9kb2kub3JnLzEwLjc5MTAvRFZOL1ZPUUNIUQ0KZWxlY3Rpb25zID0gcmVhZC5jc3YoIi4uL0RhdGEvY291bnR5cHJlc18yMDAwLTIwMTYuY3N2IikgJT4lICMgcmVhZGluZyB0aGUgZG93bmxvYWRlZCBDU1YNCiAgZmlsdGVyKHllYXIgPT0gMjAxNiAmIHBhcnR5ID09ICJyZXB1YmxpY2FuIikgJT4lICMganVzdCBrZWVwaW5nIGRhdGEgZm9yIHJlY2VudCBlbGVjdGlvbiBhbmQgcmVwdWJsaWNhbiB2b3Rlcw0KICBtdXRhdGUoa2V5X251bWVyaWMgPSBGSVBTLCAjIHJlbmFtaW5nIEZJUFMgdG8ga2V5X251bWVyaWMNCiAgICAgICAgIHBlcmNSZXBWb3RlcyA9IDEwMCooY2FuZGlkYXRldm90ZXMvdG90YWx2b3RlcykgKSAlPiUgIyBjb21wdXRpbmcgcGVyY2VudCBvZiByZXB1YmxpY2FuIHZvdGVzIChmcm9tIHRvdGFsIHZvdGVzKQ0KICBzZWxlY3Qoa2V5X251bWVyaWMsIHBlcmNSZXBWb3RlcykgIyBrZWVwaW5nIG9ubHkgdGhlIGtleSBhbmQgdmFyaWFibGUgdXNlZCBpbiBtZXJnZQ0KY3Jvc3NTZWN0aW9uYWxEYXRhICU8PiUgIG1lcmdlKGVsZWN0aW9ucywgYnkgPSAia2V5X251bWVyaWMiLCBhbGwueCA9IFRSVUUpICMgbWVyZ2Ugd2l0aCB0aGUgY291bnRpZXMgZGF0YQ0KDQoNCiMgW0RdIFBvbGljeSBEYXRhDQpwb2xpY3kgPSByZWFkX2NzdignaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL094Q0dSVC9VU0EtY292aWQtcG9saWN5L21hc3Rlci9kYXRhL094Q0dSVF9VU19sYXRlc3QuY3N2JykNCnBvbGljeSA9IGZpbHRlcihwb2xpY3ksICFpcy5uYShSZWdpb25OYW1lKSB8ICFSZWdpb25OYW1lICVpbiUgYygnQWxhc2thJywgJ0hhd2FpaScpKQ0KcG9saWN5JHN0YXRlID0gdG91cHBlcihwb2xpY3kkUmVnaW9uTmFtZSkgIyBhIHN0YXRlIHZhcmlhYmxlID0gYW4gdXBwZXIgY2FzZSBvZiBleGlzdGluZyBSZWdpb25OYW1lDQpwb2xpY3kkRGF0ZSAlPD4lIHltZCgpICMgY29udmVydGluZyB0aGUgRGF0ZSBkYXRhIHRvIGEgZGF0ZSBmb3JtYXQNCg0KcG9saWN5U3VtbWFyeSA9IHBvbGljeSAlPiUgIyBjYWxjdWxhdGluZyBhIHN1bW1hcnkgdGFibGUgb2YgbWVkaWFuIHZhbHVlIGZvciB0aGUgR292ZXJubWVudFJlc3BvbnNlSW5kZXggcGVyIHN0YXRlDQogIGZpbHRlcihEYXRlID49ICcyMDIwLTAzLTAxJyAmIERhdGUgPD0gZW5kRGF0ZSkgJT4lICMgdG8gbWF0Y2ggb3VyIENPVklEIERhdGEgdGltZVNlcmllcw0KICBncm91cF9ieShzdGF0ZSkgJT4lICMgcGVyZm9ybSBjb21wdXRhdGlvbnMgdXNpbmcgdGhlIG1lZGlhbiB2YWx1ZSwgcGVyIHN0YXRlLCBmb3IgZWFjaCBpbmRleA0KICBzdW1tYXJpc2UoR292ZXJubWVudFJlc3BvbnNlSW5kZXhNZWRpYW4gPSBtZWRpYW4oR292ZXJubWVudFJlc3BvbnNlSW5kZXgsIG5hLnJtID0gVFJVRSkpDQpwb2xpY3lTdW1tYXJ5JHN0YXRlICU8PiUgIHN0cl9yZXBsYWNlKCdXQVNISU5HVE9OIERDJywgJ0RJU1RSSUNUIE9GIENPTFVNQklBJykgJT4lIHN0cl90b190aXRsZSgpDQoNCmNyb3NzU2VjdGlvbmFsRGF0YSAlPD4lICBtZXJnZShwb2xpY3lTdW1tYXJ5LCBieS54ID0gImFkbWluaXN0cmF0aXZlX2FyZWFfbGV2ZWxfMiIsIGJ5LnkgPSAnc3RhdGUnLCBhbGwueCA9IFRSVUUpIA0KDQoNCiMgW0VdIEthaXNlciBIZWFsdGggTmV3cyBEYXRhIG9uIHRoZSBDb3VudHkgTGV2ZWwNCmhvc3BpdGFscyA9IHJlYWQuY3N2KCIuLi9EYXRhL2RhdGEtRlBCZlouY3N2IikgJT4lICMgZG93bmxvYWRlZCBmcm9tIEtITiBvbiAyMDIwLTEwLTI2ICh+OTozMCBwbSBFRFQpDQogIHRyYW5zbXV0ZShTdGF0ZSA9IFN0YXRlLCAjIGtlZXBpbmcgdGhlIFN0YXRlIFZhcmlhYmxlIHwgdHJhbnNtdXRlIGRyb3BzIHZhcmlhYmxlcyB0aGF0IGFyZSBub3QgaW4gY2FsbA0KICAgICAgICAgICAgQ291bnR5ID0gQ291bnR5LCAjIGtlZXBpbmcgdGhlIENvdW50eSBWYXJpYWJsZQ0KICAgICAgICAgICAgUGVyY2VudFNlbmlvcnMgPSBQZXJjZW50Lm9mLlBvcHVsYXRpb24uQWdlZC42MC4sICMgU2hvcnRlbmluZyBPcmlnaW5hbCBWYXJpYWJsZSBOYW1lDQogICAgICAgICAgICBpY3VCZWRzUGVyMTAwMDBTZW5pb3JzID0gMTAwMDAgKiBJQ1UuQmVkcy9Qb3B1bGF0aW9uLkFnZWQuNjAuKSAjIENvbXB1dGluZyBpY3VCZWRzUGVyMTAwMDBTZW5pb3JzDQoNCmNyb3NzU2VjdGlvbmFsRGF0YSAlPD4lIG1lcmdlKGhvc3BpdGFscywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieS54ID0gYygiYWRtaW5pc3RyYXRpdmVfYXJlYV9sZXZlbF8yIiwgImFkbWluaXN0cmF0aXZlX2FyZWFfbGV2ZWxfMyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkueSA9IGMoIlN0YXRlIiwgIkNvdW50eSIpLCBhbGwueCA9IFRSVUUpDQoNCg0KIyBbRl0gQ0RDIFJlZ2lvbnMgZm9yIEVhY2ggU3RhdGUNCnJlZ2lvbnNDREMgPSBkYXRhLmZyYW1lKFN0YXRlcyA9IGMoJ0Nvbm5lY3RpY3V0JywgJ01haW5lJywgJ01hc3NhY2h1c2V0dHMnLCAnTmV3IEhhbXBzaGlyZScsICdSaG9kZSBJc2xhbmQnICwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdWZXJtb250JywgJ05ldyBZb3JrJywgIyBFbmQgb2YgUmVnaW9uIEENCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0RlbGF3YXJlJywgJ0Rpc3RyaWN0IG9mIENvbHVtYmlhJywgJ01hcnlsYW5kJywgJ1Blbm5zeWx2YW5pYScsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdWaXJnaW5pYScsICdXZXN0IFZpcmdpbmlhJywgJ05ldyBKZXJzZXknLCAjIEVuZCBvZiBSZWdpb24gQg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnTm9ydGggQ2Fyb2xpbmEnLCAnU291dGggQ2Fyb2xpbmEnLCAnR2VvcmdpYScsICdGbG9yaWRhJywgIyBSZWdpb24gQw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnS2VudHVja3knLCAnVGVubmVzc2VlJywgJ0FsYWJhbWEnLCAnTWlzc2lzc2lwcGknLCAjIFJlZ2lvbiBEDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdJbGxpbm9pcycsICdJbmRpYW5hJywgJ01pY2hpZ2FuJywgJ01pbm5lc290YScsICdPaGlvJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1dpc2NvbnNpbicsICMgRW5kIG9mIFJlZ2lvbiBFDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdBcmthbnNhcycsICdMb3Vpc2lhbmEnLCAnTmV3IE1leGljbycsICdPa2xhaG9tYScsICdUZXhhcycsICMgUmVnaW9uIEYNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0lvd2EnLCAnS2Fuc2FzJywgJ01pc3NvdXJpJywgJ05lYnJhc2thJywgIyBSZWdpb24gRw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnQ29sb3JhZG8nLCAnTW9udGFuYScsICdOb3J0aCBEYWtvdGEnLCAnU291dGggRGFrb3RhJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1V0YWgnLCAnV3lvbWluZycsICMgRW5kIG9mIFJlZ2lvbiBIDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdBcml6b25hJywgJ0NhbGlmb3JuaWEnLCAnSGF3YWlpJywgJ05ldmFkYScsICMgUmVnaW9uIEkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0FsYXNrYScsICdJZGFobycsICdPcmVnb24nLCAnV2FzaGluZ3RvbicgIyBSZWdpb24gSg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLA0KICAgICAgICAgICAgICAgICAgICAgICAgcmVnaW9ucyA9IGMocmVwKCdBJywgNyksIHJlcCgnQicsIDcpLCByZXAoJ0MnLCA0KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcCgnRCcsIDQpLCByZXAoJ0UnLCA2KSwgcmVwKCdGJywgNSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXAoJ0cnLCA0KSwgcmVwKCdIJywgNiksIHJlcCgnSScsIDQpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwKCdKJywgNCkgKSApDQoNCmNyb3NzU2VjdGlvbmFsRGF0YSAlPD4lIG1lcmdlKHJlZ2lvbnNDREMsIGJ5LnggPSAnYWRtaW5pc3RyYXRpdmVfYXJlYV9sZXZlbF8yJywgYnkueSA9ICdTdGF0ZXMnLCBhbGwueCA9IFRSVUUpICMgbWVyZ2UNCg0KDQojIFtHXSBQb3ZlcnR5IEVzdGltYXRlcw0KZG93bmxvYWQuZmlsZSgiaHR0cHM6Ly93d3cyLmNlbnN1cy5nb3YvcHJvZ3JhbXMtc3VydmV5cy9zYWlwZS9kYXRhc2V0cy8yMDE4LzIwMTgtc3RhdGUtYW5kLWNvdW50eS9lc3QxOGFsbC54bHMiLCANCiAgICAgICAgICAgICAgZGVzdGZpbGUgPSAiLi4vRGF0YS9lc3QxOGFsbC54bHMiLCBtb2RlID0gIndiIikgIyBkb3dubG9hZGluZyB0aGUgZGF0YSBmb3IgcG92ZXJ0eSBlc3RpbWF0ZXMgKGxhdGVzdCAyMDE4KQ0KDQpwb3ZlcnR5ID0gcmVhZF9leGNlbCgiLi4vRGF0YS9lc3QxOGFsbC54bHMiLCBza2lwID0gMykgJT4lICMgcmVhZGluZyB0aGUgZGF0YSBpbiBSDQogIHRyYW5zbXV0ZShrZXlfbnVtZXJpYyA9IHBhc3RlMChgU3RhdGUgRklQUyBDb2RlYCwgYENvdW50eSBGSVBTIENvZGVgKSAlPiUgYXMubnVtZXJpYywgIyBjcmVhdGluZyB0aGUga2V5IGZyb20gdHdvIHZhcmlhYmxlcw0KICAgICAgICAgICAgcG92ZXJ0eVBlcmNlbnQgPSBhcy5udW1lcmljKGBQb3ZlcnR5IFBlcmNlbnQsIEFsbCBBZ2VzYCkgKSAjIHNob3J0ZW5pbmcgcG92ZXJ0eVBlcmNlbnQgVmFyaWFibGUncyBOYW1lDQpjcm9zc1NlY3Rpb25hbERhdGEgJTw+JSBtZXJnZShwb3ZlcnR5LCBieSA9ICJrZXlfbnVtZXJpYyIsIGFsbC54ID0gVFJVRSkgIyBtZXJnZQ0KDQoNCiMgRmluYWwgVHJhbnNmb3JtYXRpb25zIGJlZm9yZSBTYXZpbmcgdGhlIENvdW50aWVzIERhdGENCmNyb3NzU2VjdGlvbmFsRGF0YSAlPD4lICBtdXRhdGVfYXQoLnZhcnMgPSBjKCdjb3VudHlUeXBlJywgJ3JlZ2lvbnMnKSwgYXMuZmFjdG9yKSAgIyBjb252ZXJ0aW5nIHRoZSB0d28gdmFycyB0byBmYWN0b3INCg0KIyBTYXZpbmcgdGhlIGRhdGEgaW50byBhbiBSRFMgZmlsZQ0Kc2F2ZVJEUyhjcm9zc1NlY3Rpb25hbERhdGEsIHBhc3RlMCgiLi4vRGF0YS9jcm9zc1NlY3Rpb25hbERhdGEucmRzIikpDQoNCiMgVGFidWxhdGluZyB0aGUgcmVzdWx0cyBhbmQgcHJvdmlkaW5nIGEgd2F5IHRvIGV4cG9ydCB0aGUgdGFibGUgdG8gZGlmZmVyZW50IGZvcm1hdHMNCmRhdGF0YWJsZShjcm9zc1NlY3Rpb25hbERhdGEgJT4lIHNlbGVjdCgtYyhpZCwga2V5X251bWVyaWMsIGFkbWluaXN0cmF0aXZlX2FyZWFfbGV2ZWxfMiwgYWRtaW5pc3RyYXRpdmVfYXJlYV9sZXZlbF8zKSksDQogICAgICAgICAgZXh0ZW5zaW9ucyA9IGMoJ0ZpeGVkQ29sdW1ucycsICdCdXR0b25zJyksIG9wdGlvbnMgPSBsaXN0KA0KICAgICAgICAgICAgZG9tID0gJ0JmcnRpcCcsDQogICAgICAgICAgICBzY3JvbGxYID0gVFJVRSwNCiAgICAgICAgICAgIGJ1dHRvbnMgPSBjKCdjb3B5JywgJ2NzdicsICdleGNlbCcsICdwZGYnKSwNCiAgICAgICAgICAgIGZpeGVkQ29sdW1ucyA9IGxpc3QobGVmdENvbHVtbnMgPSAxKSksDQogICAgICAgICAgcm93bmFtZXMgPSBGQUxTRSkgJT4lIA0KICBmb3JtYXRSb3VuZChjb2x1bW5zPSBjKCdwb3BEZW5zaXR5JywgJ3BlcmNSZXBWb3RlcycsICdHb3Zlcm5tZW50UmVzcG9uc2VJbmRleE1lZGlhbicsDQogICAgICAgICAgICAgICAgICAgICAgICAgJ1BlcmNlbnRTZW5pb3JzJywgJ2ljdUJlZHNQZXIxMDAwMFNlbmlvcnMnLCAncG92ZXJ0eVBlcmNlbnQnKSwNCiAgICAgICAgICAgICAgZGlnaXRzPTEpDQpgYGANCg0KDQojIyBFeHBsb3JhdG9yeSBBbmFseXNpcw0KDQpJbiB0aGlzIHNlY3Rpb24sIHdlIHBlcmZvcm0gYW4gZXhwbG9yYXRvcnkgYW5hbHlzaXMgb24gdGhlIGRhdGEgb2J0YWluZWQgZnJvbSB0aGUgbXVsdGlwbGUgc291cmNlcy4NCg0KIyMjIEN1bXVsYXRpdmUgQ2FzZXMNCg0KYGBge3IgY3VtdWxhdGl2ZUNhc2VzRmlnfQ0Kbm9Hb29nbGVOQXMgPSBmaWx0ZXIoY3Jvc3NTZWN0aW9uYWxEYXRhLCAhaXMubmEoa2V5X2dvb2dsZV9tb2JpbGl0eSkpICMgcmVtb3ZpbmcgTkFzIGZyb20ga2V5X2dvb2dsZV9tb2JpbGl0eQ0KaWRJbmRleCA9IHNhbXBsZShub0dvb2dsZU5BcyRpZCwgOSkgIyBzYW1wbGluZyA5IGNvdW50aWVzIGJ5IGlkDQoNCiMgU2F2aW5nIGN1bXVsYXRpdmUgZGVhdGhzIGZpZ3VyZSB0byBhbiB0aWZmIGZpbGUNCnRpZmYoZmlsZW5hbWUgPSAnLi4vRmlndXJlcy9zYW1wbGVDdW11bGF0aXZlQ2FzZXMudGlmZicsDQogICAgd2lkdGggPSAxMzY2LCBoZWlnaHQgPTc2OCwgcG9pbnRzaXplID0gMTYpDQpjb3VudGllcyAlPiUgZmlsdGVyKGlkICVpbiUgaWRJbmRleCkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBkYXRlLCB5ID0gY29uZmlybWVkLCBncm91cCA9IGlkLCBjb2xvciA9IGtleV9nb29nbGVfbW9iaWxpdHkpKSArDQogIGdlb21fbGluZShzaXplID0gMS4yNSkgKw0KICBzY2FsZV94X2RhdGUoZGF0ZV9icmVha3MgPSAiMSBtb250aCIsIGRhdGVfbGFiZWxzID0gIiViIikgKw0KICBmYWNldF93cmFwKH4ga2V5X2dvb2dsZV9tb2JpbGl0eSwgc2NhbGVzID0gJ2ZyZWVfeScsIG5jb2wgPSAzKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJykgKyANCiAgbGFicyhjb2xvciA9ICcnLCB4ID0gJ01vbnRoJywgeSA9ICdDdW11bGF0aXZlIENhc2VzIEJ5IENvdW50eScsDQogICAgICAgY2FwdGlvbiA9IHBhc3RlMCgnQmFzZWQgb24gRGF0YSBmcm9tIE1hcmNoIDAxLCAyMDIwIC0gJywgZW5kRGF0ZVByaW50VikpICsNCiAgc2NhbGVfY29sb3JfYnJld2VyKHR5cGUgPSAncXVhbCcsIHBhbGV0dGUgPSAnUGFpcmVkJykNCmludmlzaWJsZSggZGV2Lm9mZigpICkgIyB0byBzdXBwcmVzcyB0aGUgdW53YW50ZWQgb3V0cHV0IGZyb20gZGV2Lm9mZg0KDQojIENyZWF0aW5nIGFuIGludGVyYWN0aXZlIHBsb3QgZm9yIHRoZSBtYXJrZG93bg0KcCA9IGdncGxvdDI6Omxhc3RfcGxvdCgpICsgZ2VvbV9saW5lKHNpemUgPSAwLjc1KSArICMgbW9kaWZ5aW5nIHRoZSBwbG90IGZvciBwbG90bHkNCiAgdGhlbWVfYncoYmFzZV9zaXplID0gOSkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnbm9uZScpICMgdG8gbWFrZSBtYXJnaW5zIHNtYWxsZXINCmdncGxvdGx5KHAsIGhlaWdodCA9IDc2OCkgJT4lICBsYXlvdXRfZ2dwbG90bHkoKQ0KDQpgYGANCg0KDQojIyMgTmV3IERhaWx5IENhc2VzDQoNCmBgYHtyIG5ld0Nhc2VzRmlnfQ0KIyBTYXZpbmcgbmV3IGRhaWx5IGNhc2VzIGZpZ3VyZSB0byBhbiB0aWZmIGZpbGUNCnRpZmYoZmlsZW5hbWUgPSAnLi4vRmlndXJlcy9zYW1wbGVOZXdEYWlseUNhc2VzLnRpZmYnLA0KICAgIHdpZHRoID0gMTM2NiwgaGVpZ2h0ID03NjgsIHBvaW50c2l6ZSA9IDE2KQ0KY291bnRpZXMgJT4lIGZpbHRlcihpZCAlaW4lIGlkSW5kZXgpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gZGF0ZSwgeSA9IG5ld0Nhc2VzLCBncm91cCA9IGlkLCBjb2xvciA9IGtleV9nb29nbGVfbW9iaWxpdHkpKSArDQogIGdlb21fbGluZShzaXplID0gMS4yNSkgKw0KICBzY2FsZV94X2RhdGUoZGF0ZV9icmVha3MgPSAiMSBtb250aCIsIGRhdGVfbGFiZWxzID0gIiViIikgKw0KICBmYWNldF93cmFwKH4ga2V5X2dvb2dsZV9tb2JpbGl0eSwgc2NhbGVzID0gJ2ZyZWVfeScsIG5jb2wgPSAzKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJykgKyANCiAgbGFicyhjb2xvciA9ICcnLCB4ID0gJ01vbnRoJywgeSA9ICdOZXcgRGFpbHkgQ2FzZXMgQnkgQ291bnR5JywNCiAgICAgICBjYXB0aW9uID0gcGFzdGUwKCdCYXNlZCBvbiBEYXRhIGZyb20gTWFyY2ggMDEsIDIwMjAgLSAnLCBlbmREYXRlUHJpbnRWKSkgKw0KICBzY2FsZV9jb2xvcl9icmV3ZXIodHlwZSA9ICdxdWFsJywgcGFsZXR0ZSA9ICdQYWlyZWQnKQ0KaW52aXNpYmxlKCBkZXYub2ZmKCkgKSAjIHRvIHN1cHByZXNzIHRoZSB1bndhbnRlZCBvdXRwdXQgZnJvbSBkZXYub2ZmDQoNCiMgQ3JlYXRpbmcgYW4gaW50ZXJhY3RpdmUgcGxvdCBmb3IgdGhlIG1hcmtkb3duDQpwID0gZ2dwbG90Mjo6bGFzdF9wbG90KCkgKyBnZW9tX2xpbmUoc2l6ZSA9IDAuNzUpICsgIyBtb2RpZnlpbmcgdGhlIHBsb3QgZm9yIHBsb3RseQ0KICB0aGVtZV9idyhiYXNlX3NpemUgPSA5KSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJykgIyB0byBtYWtlIG1hcmdpbnMgc21hbGxlcg0KZ2dwbG90bHkocCwgaGVpZ2h0ID0gNzY4KSAlPiUgIGxheW91dF9nZ3Bsb3RseSgpDQpgYGANCg0KDQojIyMgQ291bnR5IFR5cGVzDQoNCmBgYHtyIGNvdW50eVR5cGVzfQ0KY3Jvc3NTZWN0aW9uYWxEYXRhJGZpcHMgPSBzdHJfcGFkKGNyb3NzU2VjdGlvbmFsRGF0YSRrZXlfbnVtZXJpYywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWR0aCA9IDUsIHNpZGUgPSAnbGVmdCcsIHBhZCA9ICcwJykNCiMgUmV0cmlldmluZyB0aGUgVS5TLiBjb3VudHkgY29tcG9zaXRlIG1hcCBhcyBhIHNpbXBsZWZlYXR1cmUNCmN0eV9zZiA9IGNvdW50aWVzX3NmKCJhZXFkIikgJT4lIGZpbHRlcighc3RhdGUgJWluJSBjKCdBbGFza2EnLCAnSGF3YWlpJykpICMgZnJvbSBhbGJlcnN1YQ0KY3R5X3NmICU8PiUgZ2VvX2pvaW4oY3Jvc3NTZWN0aW9uYWxEYXRhLCBieV9zcD0gJ2ZpcHMnLCBieV9kZj0gJ2ZpcHMnKQ0KDQojIFNhdmluZyBhIGhpZ2hlciBxdWFsaXR5IHRpZmYgZmlsZSBmb3IgdXNlIGluIHRoZSBwYXBlcg0KdGlmZihmaWxlbmFtZSA9ICcuLi9GaWd1cmVzL2NvdW50eVR5cGVzLnRpZmYnLCB3aWR0aCA9IDEzNjYsIGhlaWdodCA9NzY4LCBwb2ludHNpemUgPSAxNikNCnRtX3NoYXBlKGN0eV9zZikgKyB0bV9wb2x5Z29ucygnY291bnR5VHlwZScsIHRpdGxlID0gJ0NvdW50eSBUeXBlJywgcGFsZXR0ZSA9ICJQYWlyZWQiKQ0KaW52aXNpYmxlKCBkZXYub2ZmKCkgKSAjIHRvIHN1cHByZXNzIHRoZSB1bndhbnRlZCBvdXRwdXQgZnJvbSBkZXYub2ZmDQoNCiMgUHJpbnRpbmcgdGhlIHBuZyBvdXRwdXQgaW4gdGhlIE1hcmtkb3duIGRvYw0KdG1fc2hhcGUoY3R5X3NmKSArIHRtX3BvbHlnb25zKCdjb3VudHlUeXBlJywgdGl0bGUgPSAnQ291bnR5IFR5cGUnLCBwYWxldHRlID0gIlBhaXJlZCIpDQpgYGANCg0KDQojIyMgUG9wdWxhdGlvbiBEZW5zaXR5DQoNCmBgYHtyIHBvcERlbnNpdHlGaWd9DQojIFNhdmluZyBhIGhpZ2hlciBxdWFsaXR5IHRpZmYgZmlsZSBmb3IgdXNlIGluIHRoZSBwYXBlcg0KdGlmZihmaWxlbmFtZSA9ICcuLi9GaWd1cmVzL3BvcERlbnNpdHkudGlmZicsIHdpZHRoID0gMTM2NiwgaGVpZ2h0ID03NjgsIHBvaW50c2l6ZSA9IDE2KQ0KdG1fc2hhcGUoY3R5X3NmKSArIHRtX3BvbHlnb25zKCdwb3BEZW5zaXR5JywgdGl0bGUgPSAnUG9wdWxhdGlvbiBEZW5zaXR5JywgcGFsZXR0ZSA9ICJHcmVlbnMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0eWxlID0gJ3F1YW50aWxlJykNCmludmlzaWJsZSggZGV2Lm9mZigpICkgIyB0byBzdXBwcmVzcyB0aGUgdW53YW50ZWQgb3V0cHV0IGZyb20gZGV2Lm9mZg0KDQojIFByaW50aW5nIHRoZSBwbmcgb3V0cHV0IGluIHRoZSBNYXJrZG93biBkb2MNCnRtX3NoYXBlKGN0eV9zZikgKyB0bV9wb2x5Z29ucygncG9wRGVuc2l0eScsIHRpdGxlID0gJ1BvcHVsYXRpb24gRGVuc2l0eScsIHBhbGV0dGUgPSAiR3JlZW5zIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZSA9ICdxdWFudGlsZScpDQpgYGANCg0KIyMjIFBlcmNlbnQgUmVwdWJsaWNhbiBWb3Rlcw0KDQpgYGB7ciBwZXJjUmVwVm90ZXNGaWd9DQojIFNhdmluZyBhIGhpZ2hlciBxdWFsaXR5IHRpZmYgZmlsZSBmb3IgdXNlIGluIHRoZSBwYXBlcg0KdGlmZihmaWxlbmFtZSA9ICcuLi9GaWd1cmVzL3JlcFZvdGVzLnRpZmYnLCB3aWR0aCA9IDEzNjYsIGhlaWdodCA9NzY4LCBwb2ludHNpemUgPSAxNikNCnRtX3NoYXBlKGN0eV9zZikgKyB0bV9wb2x5Z29ucygncGVyY1JlcFZvdGVzJywgdGl0bGUgPSAnJSBSZXB1YmxpY2FuIFZvdGVzJywgcGFsZXR0ZSA9ICJSZWRzIikNCmludmlzaWJsZSggZGV2Lm9mZigpICkgIyB0byBzdXBwcmVzcyB0aGUgdW53YW50ZWQgb3V0cHV0IGZyb20gZGV2Lm9mZg0KDQojIFByaW50aW5nIHRoZSBwbmcgb3V0cHV0IGluIHRoZSBNYXJrZG93biBkb2MNCnRtX3NoYXBlKGN0eV9zZikgKyB0bV9wb2x5Z29ucygncGVyY1JlcFZvdGVzJywgdGl0bGUgPSAnJSBSZXB1YmxpY2FuIFZvdGVzJywgcGFsZXR0ZSA9ICJSZWRzIikNCmBgYA0KDQoNCiMjIyBHb3Zlcm5tZW50IFJlc3BvbnNlIERhdGENCmBgYHtyIGdvdlJlc3BGaWd9DQpzdGF0ZV9zZiA9IHVzYV9zZigiYWVxZCIpICU+JSBmaWx0ZXIoIW5hbWUgJWluJSBjKCdBbGFza2EnLCAnSGF3YWlpJykpICMgZnJvbSBhbGJlcnN1YQ0Kc3RhdGVfc2YgJTw+JSBnZW9fam9pbihjcm9zc1NlY3Rpb25hbERhdGEsIGJ5X3NwPSAnbmFtZScsIGJ5X2RmPSAnYWRtaW5pc3RyYXRpdmVfYXJlYV9sZXZlbF8yJykNCg0KIyBTYXZpbmcgYSBoaWdoZXIgcXVhbGl0eSB0aWZmIGZpbGUgZm9yIHVzZSBpbiB0aGUgcGFwZXINCnRpZmYoZmlsZW5hbWUgPSAnLi4vRmlndXJlcy9nb3ZSZXNwb25zZS50aWZmJywgd2lkdGggPSAxMzY2LCBoZWlnaHQgPTc2OCwgcG9pbnRzaXplID0gMTYpDQp0bV9zaGFwZShzdGF0ZV9zZikgKyB0bV9wb2x5Z29ucygnR292ZXJubWVudFJlc3BvbnNlSW5kZXhNZWRpYW4nLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpdGxlID0gJ01lZGlhbiBWYWx1ZSBvZiB0aGUgR292ZXJubWVudCBSZXNwb25zZSBJbmRleCcsIHBhbGV0dGUgPSAiLUdyZWVucyIpDQppbnZpc2libGUoIGRldi5vZmYoKSApICMgdG8gc3VwcHJlc3MgdGhlIHVud2FudGVkIG91dHB1dCBmcm9tIGRldi5vZmYNCg0KIyBQcmludGluZyB0aGUgcG5nIG91dHB1dCBpbiB0aGUgTWFya2Rvd24gZG9jDQp0bV9zaGFwZShzdGF0ZV9zZikgKyB0bV9wb2x5Z29ucygnR292ZXJubWVudFJlc3BvbnNlSW5kZXhNZWRpYW4nLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpdGxlID0gJ01lZGlhbiBWYWx1ZSBvZiB0aGUgR292ZXJubWVudCBSZXNwb25zZSBJbmRleCcsIHBhbGV0dGUgPSAiLUdyZWVucyIpDQpgYGANCg0KDQojIyMgUGVyY2VudCBTZW5pb3JzDQoNCmBgYHtyIHBlcmNTZW5pb3JzRmlnfQ0KIyBTYXZpbmcgYSBoaWdoZXIgcXVhbGl0eSB0aWZmIGZpbGUgZm9yIHVzZSBpbiB0aGUgcGFwZXINCnRpZmYoZmlsZW5hbWUgPSAnLi4vRmlndXJlcy9wZXJjU2VuaW9ycy50aWZmJywgd2lkdGggPSAxMzY2LCBoZWlnaHQgPTc2OCwgcG9pbnRzaXplID0gMTYpDQp0bV9zaGFwZShjdHlfc2YpICsgdG1fcG9seWdvbnMoJ1BlcmNlbnRTZW5pb3JzJywgdGl0bGUgPSAnJSBTZW5pb3JzJywgcGFsZXR0ZSA9ICJHcmVlbnMiKQ0KaW52aXNpYmxlKCBkZXYub2ZmKCkgKSAjIHRvIHN1cHByZXNzIHRoZSB1bndhbnRlZCBvdXRwdXQgZnJvbSBkZXYub2ZmDQoNCiMgUHJpbnRpbmcgdGhlIHBuZyBvdXRwdXQgaW4gdGhlIE1hcmtkb3duIGRvYw0KdG1fc2hhcGUoY3R5X3NmKSArIHRtX3BvbHlnb25zKCdQZXJjZW50U2VuaW9ycycsIHRpdGxlID0gJyUgU2VuaW9ycycsIHBhbGV0dGUgPSAiR3JlZW5zIikNCmBgYA0KDQojIyMgQ0RDIFJlZ2lvbnMNCg0KYGBge3IgY2RjUmVnc0ZpZ30NCiMgU2F2aW5nIGEgaGlnaGVyIHF1YWxpdHkgdGlmZiBmaWxlIGZvciB1c2UgaW4gdGhlIHBhcGVyDQp0aWZmKGZpbGVuYW1lID0gJy4uL0ZpZ3VyZXMvY2RjUmVnaW9ucy50aWZmJywgd2lkdGggPSAxMzY2LCBoZWlnaHQgPTc2OCwgcG9pbnRzaXplID0gMTYpDQp0bV9zaGFwZShzdGF0ZV9zZikgKyB0bV9wb2x5Z29ucygncmVnaW9ucycsIHRpdGxlID0gJ0NEQyBSZWdpb24nLCBwYWxldHRlID0gIlBhaXJlZCIpDQppbnZpc2libGUoIGRldi5vZmYoKSApICMgdG8gc3VwcHJlc3MgdGhlIHVud2FudGVkIG91dHB1dCBmcm9tIGRldi5vZmYNCg0KIyBQcmludGluZyB0aGUgcG5nIG91dHB1dCBpbiB0aGUgTWFya2Rvd24gZG9jDQp0bV9zaGFwZShzdGF0ZV9zZikgKyB0bV9wb2x5Z29ucygncmVnaW9ucycsIHRpdGxlID0gJ0NEQyBSZWdpb24nLCBwYWxldHRlID0gIlBhaXJlZCIpDQpgYGANCg0KIyMjIFBlcmNlbnQgUG92ZXJ0eQ0KDQpgYGB7ciBwb3ZlcnR5UGVyY2VudEZpZ30NCiMgU2F2aW5nIGEgaGlnaGVyIHF1YWxpdHkgdGlmZiBmaWxlIGZvciB1c2UgaW4gdGhlIHBhcGVyDQp0aWZmKGZpbGVuYW1lID0gJy4uL0ZpZ3VyZXMvcG92ZXJ0eVBlcmNlbnQudGlmZicsIHdpZHRoID0gMTM2NiwgaGVpZ2h0ID03NjgsIHBvaW50c2l6ZSA9IDE2KQ0KdG1fc2hhcGUoY3R5X3NmKSArIHRtX3BvbHlnb25zKCdwb3ZlcnR5UGVyY2VudCcsIHRpdGxlID0gJ1BvdmVydHkgUGVyY2VudCcsIHBhbGV0dGUgPSAiR3JlZW5zIikNCmludmlzaWJsZSggZGV2Lm9mZigpICkgIyB0byBzdXBwcmVzcyB0aGUgdW53YW50ZWQgb3V0cHV0IGZyb20gZGV2Lm9mZg0KDQojIFByaW50aW5nIHRoZSBwbmcgb3V0cHV0IGluIHRoZSBNYXJrZG93biBkb2MNCnRtX3NoYXBlKGN0eV9zZikgKyB0bV9wb2x5Z29ucygncG92ZXJ0eVBlcmNlbnQnLCB0aXRsZSA9ICdQb3ZlcnR5IFBlcmNlbnQnLCBwYWxldHRlID0gIkdyZWVucyIpDQpgYGANCg0KDQojIFRpbWUtU2VyaWVzIENsdXN0ZXJpbmcgDQoNCkl0IGlzIGltcG9ydGFudCB0byBub3RlIHRoYXQsIGluIG91ciBlc3RpbWF0aW9uLCB0aGVyZSBhcmUgdGhyZWUgaW1wb3J0YW50IGRlY2lzaW9ucyB0byBiZSBtYWRlIHdoZW4gcGVyZm9ybWluZyB0aW1lLXNlcmllcyBjbHVzdGVyaW5nOiAgDQoNCiAgLSAqUHJlcGFyYXRpb24gb2YgdGhlIERpZmZlcmVudCBUaW1lLVNlcmllcyB0byBiZSBDbHVzdGVyZWQqICBJbiB0aGlzIHNlY3Rpb24sIHdlIGhhdmUgKGEpIHNlbGVjdGVkIHRoZSBuZXcgZGFpbHkgY2FzZXMgcGVyIGNvdW50eSBhcyB0aGUgcHJpbWFyeSB2YXJpYWJsZSBvZiBpbnRlcmVzdCwgKGIpIHNtb290aGVkIHRoYXQgdmFyaWFibGUgdXNpbmcgYSBzZXZlbi1kYXkgbW92aW5nIGF2ZXJhZ2UsIGFuZCAoYykgc2NhbGVkIHRoZSBvYnNlcnZhdGlvbnMgd2l0aGluIGVhY2ggY291bnR54oCZcyA3LWRheSBNQSBvZiBuZXcgZGFpbHkgZGVhdGhzIHN1Y2ggdGhhdCBpdCBpcyBib3VuZGVkIGJldHdlZW4gMCBhbmQgMS4gVGhpcyBhbGxvd3MgdXMgdG8gY29tcGFyZSB0aGUgKipzaGFwZSoqIG9mIHRoZSB0aW1lLXNlcmllcy9wcm9maWxlIGFjcm9zcyBjb3VudGllcyBvZiBkaWZmZXJlbnQgcG9wdWxhdGlvbnMgYW5kIHdoZXJlIHRoZSBtYWduaXR1ZGUgb2YgdGhlIGNhc2VzIGlzIHF1aXRlIGRpZmZlcmVudC4NCiAgDQogIC0gKkNob2ljZSBvZiBEaXN0YW5jZSBNZWFzdXJlOiBUaGUgRXVjbGlkZWFuIGRpc3RhbmNlKiwgVGhlIEV1Y2xpZGVhbiBEaXN0YW5jZSBpLmUuLCB0aGUgJGxfMiQgbm9ybSwgaXMgdGhlIG1vc3QgY29tbW9ubHkgdXNlZCBkaXN0YW5jZSBtZWFzdXJlIHNpbmNlIGl0IGlzIGNvbXB1dGF0aW9uYWxseSBlZmZpY2llbnQuIEhvd2V2ZXIsIGl0IG1heSBub3QgYmUgc3VpdGFibGUgZm9yIGFwcGxpY2F0aW9ucyB3aGVyZSB0aGUgdGltZS1zZXJpZXMgYXJlIG9mIGRpZmZlcmVudCBsZW5ndGggaW4gYWRkaXRpb24gdG8gYmVpbmcgc2Vuc2l0aXZlIHRvIG5vaXNlLCBzY2FsZSBhbmQgdGltZSBzaGlmdHMgW0BzYXJkYTIwMTdjb21wYXJpbmddLiAgDQogIA0KICAtICpDaG9pY2Ugb2YgQ2x1c3RlcmluZyBBbGdvcml0aG06KiBBIGxhcmdlIG51bWJlciBvZiBjbHVzdGVyaW5nIGFsZ29yaXRobXMgaGF2ZSBiZWVuIHByb3Bvc2VkIGluIHRoZSBsaXRlcmF0dXJlLiBNb3N0IGNvbW1vbiBjbHVzdGVyaW5nIGFwcHJvYWNoZXMgYXJlIHNoYXBlLWJhc2VkLCB3aGljaCBpbmNsdWRlICRrLSRtZWFucyBjbHVzdGVyaW5nIGFuZCBoaWVyYXJjaGljYWwgY2x1c3RlcmluZy4gVGhlIHJlYWRlciBpcyByZWZlcnJlZCB0byBAYWdoYWJvem9yZ2kyMDE1dGltZSBmb3IgYSBkZXRhaWxlZCByZXZpZXcuIEluIG91ciBwcmVsaW1pbmFyeSBhbmFseXNpcywgd2UgaGF2ZSBjaG9zZW4gdG8gdXNlIHRoZSBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyBhcHByb2FjaCBzaW5jZSBpdCBwcm92aWRlcyBhbiBlYXN5IHRvIHVuZGVyc3RhbmQgZGVuZG9ncmFtIGFuZCB0aGUgbnVtYmVyIG9mIGNvdW50aWVzIHdhcyBzbWFsbC4gSG93ZXZlciwgaW4gb3VyIGZ1bGwgYW5hbHlzaXMsIHdlIHdpbGwgdXNlIHRoZSAkay0kbWVhbnMgY2x1c3RlcmluZyBhbGdvcml0aG0gc2luY2UgaXQgaXMgY29tcHV0YXRpb25hbGx5IGVmZmljaWVudC4gRnVydGhlcm1vcmUsIHdlIG92ZXJjYW1lIHRoZSB0cmFkaXRpb25hbCBsaW1pdGF0aW9uIG9mIGhhdmluZyB0byBwcmUtc3BlY2lmeSAkayQgYnkgdXRpbGl6aW5nIDI2IGluZGV4ZXMgZm9yIGRldGVybWluaW5nIHRoZSBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycyBpbiBhIGRhdGEgc2V0IGJhc2VkIG9uIHRoZSBleGNlbGxlbnQgYXBwcm9hY2ggYW5kIHBhY2thZ2UgaW1wbGVtZW50YXRpb24gb2YgQGNoYXJyYWQyMDE0TmJDbHVzdC4NCg0KDQojIyBEYXRhIFByZXBhcmF0aW9uDQoNCmBgYHtyIGRhdGFQcmVwQ2x1c3RlcmluZ30NCmNsdXN0ZXJpbmdQcmVwID0gY291bnRpZXMgJT4lICMgZnJvbSB0aGUgY291bnRpZXMNCiAgc2VsZWN0KGlkLCBkYXRlLCBrZXlfZ29vZ2xlX21vYmlsaXR5LCBuZXdDYXNlcykgJT4lICMgc2VsZWN0aW5nIG1pbmltYWwgYW1vdW50IG9mIGNvbHMgZm9yIHZpc3VhbCBpbnNwZWN0aW9uDQogIGFycmFuZ2UoaWQsIGRhdGUpICU+JSAjIGFycmFuZ2VkIHRvIGVuc3VyZSBjb3JyZWN0IGNhbGN1bGF0aW9ucw0KICBtdXRhdGUobmV3TUE3ID0gcm9sbG1lYW5yKG5ld0Nhc2VzLCBrID0gNywgZmlsbCA9IE5BKSwgIyA3LWRheSBtYSBvZiBuZXcgKGFkanVzdGVkKSBjYXNlcw0KICAgICAgICAgbWF4TUE3ID0gbWF4KG5ld01BNywgbmEucm0gPSBUKSwgIyBvYnRhaW5pbmcgdGhlIG1heCBwZXIgY291bnR5IHRvIHNjYWxlIGRhdGENCiAgICAgICAgIHNjYWxlZE5ld01BNyA9IHBtYXgoMCwgbmV3TUE3L21heE1BNywgbmEucm0gPSBUUlVFKSApICU+JSAjIHNjYWxpbmcgZGF0YSB0byBhIDAtMSBzY2FsZSBieSBjb3VudHkNCiAgc2VsZWN0KGlkLCBrZXlfZ29vZ2xlX21vYmlsaXR5LCBkYXRlLCBzY2FsZWROZXdNQTcpICU+JSAjIGRyb3BwaW5nIHRoZSB2YXJpYWJsZSBuZXdDYXNlcw0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gZGF0ZSwgdmFsdWVzX2Zyb20gPSBzY2FsZWROZXdNQTcpICMgY29udmVydGluZyB0aGUgZGF0YSB0byBhIHdpZGUgZm9ybWF0IGZvciBjbHVzdGVyaW5nDQoNCmNvbnN0YW50Q29sdW1ucyAgPSB3aGljaF9hcmVfY29uc3RhbnQoY2x1c3RlcmluZ1ByZXAsIHZlcmJvc2UgPSBGKSAjIGlkZW50aWZ5aW5nIGNvbnN0YW50IGNvbHVtbnMNCmRhdGVzRHJvcHBlZCA9IGNvbG5hbWVzKGNsdXN0ZXJpbmdQcmVwKVtjb25zdGFudENvbHVtbnNdICMgdXNlZCBmb3IgcHJpbnRpbmcgdGhlIG5hbWVzIGFmdGVyIHRoZSBjb2RlIGNodW5rDQoNCmNsdXN0ZXJpbmdQcmVwICU8PiUgc2VsZWN0KC1hbGxfb2YoY29uc3RhbnRDb2x1bW5zKSApICU+JSAgIyBzcGVlZHMgdXAgY2x1c3RlcmluZyBieSBkZWMgbGVuZ3RoIG9mIHNlcmllcw0KICBhcy5kYXRhLmZyYW1lKCkgIyBkYXRhIG5lZWRzIHRvIGJlIGRhdGEgZnJhbWUgZm9yIGNsdXN0ZXJpbmcNCnJvdy5uYW1lcyhjbHVzdGVyaW5nUHJlcCkgPSBjbHVzdGVyaW5nUHJlcFssMV0gIyBuZWVkZWQgZm9yIHRzY2x1c3QNCmNsdXN0ZXJpbmdQcmVwID0gY2x1c3RlcmluZ1ByZXBbLC0xXSAjIGRyb3BwaW5nIHRoZSBpZCBjb2x1bW4gc2luY2UgaXQgaXMgbm93IHJvdy5uYW1lDQpgYGANCg0KVGhlIGZvbGxvd2luZyBkYXRlcyB3ZXJlIHJlbW92ZWQgZnJvbSBvdXIgZGF0YSBmcmFtZSBzaW5jZSB0aGUgYHNjYWxlZE5FV01BN2AgdmFyaWFibGUgd2FzIGNvbnN0YW50IGFjcm9zcyBhbGwgY291bnRpZXM6IGByIHBhbmRlcihkYXRlc0Ryb3BwZWQsIGNvbXBhY3QgPSBUUlVFKWAuDQoNCg0KIyMgQ2x1c3RlcmluZyBDb250aWd1b3VzIFUuUy4gQ291bnRpZXMNCg0KYGBge3IgdHNDbHVzdGVyaW5nLCBmaWcuc2hvdz0naGlkZSd9DQpjbHVzdGVyaW5nUHJlcCAlPD4lIHNlbGVjdCgtYyhrZXlfZ29vZ2xlX21vYmlsaXR5KSkgICMgcmVtb3ZpbmcgdGhpcyB2YXJpYWJsZSBzbyB3ZSBjYW4gY2x1c3Rlcg0KDQpuYyAgPSBOYkNsdXN0KGNsdXN0ZXJpbmdQcmVwLCBkaXN0YW5jZSA9ICJldWNsaWRlYW4iLCAjIGV1Y2xpZGVhbiBkaXN0YW5jZQ0KICAgICAgICAgICAgIG1pbi5uYyA9IDIsIG1heC5uYyA9IDQ5LCAjIHNlYXJjaGluZyBmb3Igb3B0aW1hbCBrIGJldHdlZW4gaz0yIGFuZCBrPTQ5DQogICAgICAgICAgICAgbWV0aG9kID0gImttZWFucyIsICMgdXNpbmcgdGhlIGstbWVhbnMgbWV0aG9kDQogICAgICAgICAgICAgaW5kZXggPSAiYWxsIikgIyB1c2luZyAyNiBvZiB0aGUgMzAgaW5kaWNlcyBpbiB0aGUgcGFja2FnZQ0KDQprY2x1cyAgPSBuYyRCZXN0LnBhcnRpdGlvbiAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSAjb2J0YWluaW5nIHRoZSBiZXN0IHBhcnRpdGlvbi8gY2x1c3RlciBhc3NpZ25tZW50IGZvciBvcHRpbWFsIGsNCiAgcmVuYW1lKC4sIGNsdXN0ZXJfZ3JvdXAgPSAuKSAlPiUgcm93bmFtZXNfdG9fY29sdW1uKCJDb3VudHkiKSANCg0KI2NvbnZlcnRpbmcgdGhlIHdpZGUgdG8gdGFsbCBkYXRhIGFuZCBhZGRpbmcgdGhlIGNsdXN0ZXIgZ3JvdXBpbmdzDQpjbHVzdGVycyAgPSBjbHVzdGVyaW5nUHJlcCAlPiUgDQogIHJvd25hbWVzX3RvX2NvbHVtbih2YXIgPSAiQ291bnR5IikgJT4lIA0KICBwaXZvdF9sb25nZXIoY29scyA9IHN0YXJ0c193aXRoKCIyMDIwIiksIG5hbWVzX3RvID0gIkRhdGUiKSAlPiUgDQogIGlubmVyX2pvaW4oLiwga2NsdXMsIGJ5ID0gIkNvdW50eSIpICU+JSANCiAgbXV0YXRlKGNsdXN0ZXJfZ3JvdXAgPSBhcy5mYWN0b3IoY2x1c3Rlcl9ncm91cCkpDQoNCmlkQ2x1c3RlcnMgID0gY2x1c3RlcnMgJT4lIHNlbGVjdChjKENvdW50eSwgY2x1c3Rlcl9ncm91cCkpICMgY3JlYXRpbmcgYSBsb29rLXVwIHRhYmxlIG9mIGNvdW50eSBhbmQgY2x1c3RlciBncm91cA0KY29sbmFtZXMoaWRDbHVzdGVycykgID0gYygnaWQnLCAnY2x1c3Rlcl9ncm91cCcpICMgcmVuYW1pbmcgdGhlIGNvbHVtbnMNCmlkQ2x1c3RlcnMgJTw+JSAgdW5pcXVlKCkgI3JlbW92aW5nIHRoZSBkdXBsaWNhdGVzIGR1ZSB0byBkaWZmZXJlbnQgZGF0ZXMgKHdlIGhhZCB0aGF0IHRvIGVuc3VyZSB0aGF0IHRoZSBjbHVzdGVyaW5nIHdhcyBhcHBsaWVkIGNvcnJlY3RseSkNCg0KIyBBZGRpbmcgQ2x1c3RlciBHcm91cGluZyB0byBhIHN1YnNldCBvZiB0aGUgY291bnRpZXMgZGF0YSBmcmFtZQ0KY2x1c3RlckNvdW50aWVzID0gY291bnRpZXMgJT4lIA0KICBzZWxlY3QoYyhpZCwga2V5X251bWVyaWMsIGtleV9nb29nbGVfbW9iaWxpdHksIGFkbWluaXN0cmF0aXZlX2FyZWFfbGV2ZWxfMiwgYWRtaW5pc3RyYXRpdmVfYXJlYV9sZXZlbF8zKSkgJT4lIA0KICBpbm5lcl9qb2luKC4sIGlkQ2x1c3RlcnMsIGJ5ID0naWQnKSAlPiUgDQogIG11dGF0ZShjbHVzdGVyX2dyb3VwID0gcGFzdGUwKCdDJywgY2x1c3Rlcl9ncm91cCkpICU+JSANCiAgdW5pcXVlKCkNCg0KIyBzYXZpbmcgdGhlIHJlc3VsdHMgYXMgYSBSRFMgRmlsZQ0Kc2F2ZVJEUyhjbHVzdGVyQ291bnRpZXMsICcuLi9EYXRhL2NsdXN0ZXJDb3VudGllcy5yZHMnKQ0KYGBgDQoNCg0KIyMgVmlzdWFsaXppbmcgdGhlIENsdXN0ZXJpbmcgUmVzdWx0cw0KDQpJbiB0aGlzIHN1YnNlY3Rpb24sIHdlIHByb3ZpZGUgdGhyZWUgcGxvdHM6ICANCg0KICAtIEEgcGFuZWxlZCBzcGFnaGV0dGkgcGxvdCwgaGlnaGxpZ2h0aW5nIHRoZSBtZWRpYW4gc2NhbGVkIHRpbWUtc2VyaWVzIGZvciBwcm9maWxlIGZvciBlYWNoIGNsdXN0ZXI7ICANCiAgLSBBIHBhbmVsIHBsb3Qgd2hlcmUgdGhlIGZpcnN0LCBzZWNvbmQgYW5kIHRoaXJkIHF1YXJ0aWxlcyBvZiB0aGUgc2NhbGVkIHRpbWUtc2VyaWVzIGZvciBlYWNoIGNsdXN0ZXIgYXJlIGNvbXBhcmVkOyBhbmQgIA0KICAtIEFuIGludGVyYWN0aXZlIGNobG9yb3BsZXRoIG1hcHMgdG8gdmlzdWFsaXplIHRoZSBzcGF0aWFsIGRpc3RyaWJ1dGlvbiBvZiB0aGUgY2x1c3RlcnMsIHdoZXJlIHRoZSByZWFkZXIgY2FuIGNsaWNrIG9uIGEgZ2l2ZW4gY291bnR5IHRvIHNob3c6IChhKSBjb3VudHkgbmFtZSwgKGIpIGFzc2lnbmVkIGNsdXN0ZXIsIChjKSBwb3B1bGF0aW9uIGRlbnNpdHksIGFuZCAoZCkgcGVyY2VudGFnZSBvZiByZXNpZGVudHMgaW4gcG92ZXJ0eS4NCg0KDQojIyMgU3BhZ2hldHRpIFBsb3QNCg0KYGBge3Igc3BhZ2hldHRpfQ0Kc3BhZ2hldHRpREYgPSBjb3VudGllcyAlPiUgIyBmcm9tIHRoZSBjb3VudGllcw0KICBzZWxlY3QoaWQsIGRhdGUsIG5ld0Nhc2VzLCBrZXlfZ29vZ2xlX21vYmlsaXR5KSAlPiUgIyBzZWxlY3RpbmcgbWluaW1hbCBjb2x1bW5zDQogIGxlZnRfam9pbihjbHVzdGVyQ291bnRpZXNbLCBjKCdpZCcsICdjbHVzdGVyX2dyb3VwJyldLCBieSA9ICdpZCcpICU+JSAjIHRvIGdldCBjbHVzdGVycw0KICBhcnJhbmdlKGlkLCBkYXRlKSAlPiUgIyBhcnJhbmdlZCB0byBlbnN1cmUgY29ycmVjdCBjYWxjdWxhdGlvbnMNCiAgbXV0YXRlKG5ld01BNyA9IHJvbGxtZWFucihuZXdDYXNlcywgayA9IDcsIGZpbGwgPSBOQSksICMgNy1kYXkgbWEgb2YgbmV3IChhZGp1c3RlZCkgZGVhdGhzDQogICAgICAgICBtYXhNQTcgPSBtYXgobmV3TUE3LCBuYS5ybSA9IFQpLCAjIG9idGFpbmluZyB0aGUgbWF4IHBlciBjb3VudHkgdG8gc2NhbGUgZGF0YQ0KICAgICAgICAgc2NhbGVkTmV3TUE3ID0gcG1heCgwLCBuZXdNQTcvbWF4TUE3LCBuYS5ybSA9IFRSVUUpICkgJT4lIA0KICB1bmdyb3VwKCkgJT4lIHNlbGVjdChkYXRlLCBjbHVzdGVyX2dyb3VwLCBzY2FsZWROZXdNQTcsIGtleV9nb29nbGVfbW9iaWxpdHkpICU+JSANCiAgZ3JvdXBfYnkoZGF0ZSwgY2x1c3Rlcl9ncm91cCkNCg0Kc3BhZ2hldHRpREYkY2x1c3Rlcl9ncm91cCAlPD4lIGFzLmZhY3RvcigpIA0KDQojIENyZWF0aW5nIGEgTmFtZWQgQ29sb3IgU2NhbGUNCmNvbG9yUGFsID0gIGJyZXdlci5wYWwobj0gbGV2ZWxzKHNwYWdoZXR0aURGJGNsdXN0ZXJfZ3JvdXApICU+JSBsZW5ndGgoKSwgJ1NldDInKQ0KbmFtZXMoY29sb3JQYWwpID0gbGV2ZWxzKHNwYWdoZXR0aURGJGNsdXN0ZXJfZ3JvdXApDQoNCiMgU2F2aW5nIHNwYWdoZXR0aSBwbG90IHRvIGFuIHRpZmYgZmlsZQ0KdGlmZihmaWxlbmFtZSA9ICcuLi9GaWd1cmVzL3NwYWdoZXR0aVBsb3QudGlmZicsIHdpZHRoID0gMTM2NiwgaGVpZ2h0ID03NjgsIHBvaW50c2l6ZSA9IDE2KQ0Kc3BhZ2hldHRpREYgJT4lICANCiAgZ2dwbG90KGFlcyh4ID0gZGF0ZSwgeSA9IHNjYWxlZE5ld01BNywgY29sb3IgPSBjbHVzdGVyX2dyb3VwLCBncm91cCA9IGtleV9nb29nbGVfbW9iaWxpdHkpKSArDQogIHNjYWxlX3hfZGF0ZShkYXRlX2JyZWFrcyA9ICIxIG1vbnRoIiwgZGF0ZV9sYWJlbHMgPSAiJWIiKSArDQogIGdlb21fbGluZShzaXplID0gMC4yNSwgYWxwaGEgPSAwLjEpICsNCiAgc3RhdF9zdW1tYXJ5KGFlcyhncm91cCA9IDEpLCANCiAgICAgICAgICAgICAgIGZ1bj0gbWVkaWFuLA0KICAgICAgICAgICAgICAgZ2VvbSA9ICJsaW5lIiwNCiAgICAgICAgICAgICAgIHNpemUgPSAxLjI1LCBjb2wgPSAnYmxhY2snKSArIA0KICBmYWNldF93cmFwKH4gY2x1c3Rlcl9ncm91cCwgbmNvbCA9IDEpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnKSArIA0KICBsYWJzKHggPSAnTW9udGgnLCB5ID0gJ1NjYWxlZCBOZXcgQ2FzZXMgQnkgQ2x1c3RlciBCeSBEYXknLA0KICAgICAgIGNhcHRpb24gPSBwYXN0ZTAoJ1NvbGlkIGJsYWNrIGxpbmUgcmVwcmVzZW50cyB0aGUgbWVkaWFuIGZvciBlYWNoIGNsdXN0ZXIgfCANCiAgICAgICBCYXNlZCBvbiBEYXRhIGZyb20gTWFyY2ggMDEsIDIwMjAgLSAnLCBlbmREYXRlUHJpbnRWKSApICArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjb2xvclBhbCkNCmludmlzaWJsZSggZGV2Lm9mZigpICkgIyB0byBzdXBwcmVzcyB0aGUgdW53YW50ZWQgb3V0cHV0IGZyb20gZGV2Lm9mZg0KDQojIFByaW50aW5nIGEgcG5nIHZlcnNpb24gb2YgdGhlIHBsb3QgaW4gTWFya2Rvd24gKGxvd2VyIHF1YWxpdHkgaW1hZ2UgZm9yIHF1aWNrZXIgY29tcGlsYXRpb24gb2YgSFRNTCkNCnJlYWRUSUZGKCIuLi9GaWd1cmVzL3NwYWdoZXR0aVBsb3QudGlmZiIpICU+JSBncmlkLnJhc3RlcigpDQpgYGANCg0KDQojIyMgU3VtbWFyeSBQbG90DQpgYGB7ciBzdW1tYXJ5UGxvdH0NCiMgQ3JlYXRpbmcgYSBkYXRhIGZyYW1lIGNvbnRhaW5pbmcgc3RhdGlzdGljYWwgc3VtbWFyaWVzIG9mIHRoZSB0aW1lIHNlcmllcyBieSBjbHVzdGVyX2dyb3VwDQpzdW1tYXJ5RGYgPSBzcGFnaGV0dGlERiAlPiUgDQogIHN1bW1hcmlzZShNZWRpYW4gPSBtZWRpYW4oc2NhbGVkTmV3TUE3LCBuYS5ybT0gVFJVRSksDQogICAgICAgICAgICBgRmlyc3QgUXVhcnRpbGVgID0gcXVhbnRpbGUoc2NhbGVkTmV3TUE3LCBwcm9icyA9IDAuMjUsIG5hLnJtPSBUUlVFKSwNCiAgICAgICAgICAgIGBUaGlyZCBRdWFydGlsZWAgPSBxdWFudGlsZShzY2FsZWROZXdNQTcsIHByb2JzID0gMC43NSwgbmEucm09IFRSVUUpKSAlPiUgDQogIHBpdm90X2xvbmdlcihjb2xzID0gYyhgRmlyc3QgUXVhcnRpbGVgLCBNZWRpYW4sIGBUaGlyZCBRdWFydGlsZWApLA0KICAgICAgICAgICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAnU3RhdGlzdGljJykNCg0KdGlmZihmaWxlbmFtZSA9ICcuLi9GaWd1cmVzL3N1bW1hcnlQbG90LnRpZmYnLCB3aWR0aCA9IDEzNjYsIGhlaWdodCA9NzY4LCBwb2ludHNpemUgPSAxNikNCnN1bW1hcnlEZiAlPiUgDQogIGdncGxvdChhZXMoeCA9IGRhdGUsIHkgPSB2YWx1ZSwgY29sb3IgPSBjbHVzdGVyX2dyb3VwLCBsaW5ldHlwZSA9ICBTdGF0aXN0aWMpKSArDQogIHNjYWxlX3hfZGF0ZShkYXRlX2JyZWFrcyA9ICIxIG1vbnRoIiwgZGF0ZV9sYWJlbHMgPSAiJWIiKSArDQogIGdlb21fbGluZShzaXplID0gMS4yNSkgKw0KICAgc2NhbGVfbGluZXR5cGVfbWFudWFsKHZhbHVlcyA9IGMoJ2RvdHRlZCcsICdzb2xpZCcsICd0d29kYXNoJykpICsNCiAgZmFjZXRfd3JhcCh+IGNsdXN0ZXJfZ3JvdXAsIG5jb2wgPSAxKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICd0b3AnKSArIA0KICBsYWJzKGNvbG9yID0gJycsIHggPSAnTW9udGgnLCB5ID0gJ1F1YXJ0aWxlcyBvZiBTY2FsZWQgTmV3IENhc2VzIEJ5IENsdXN0ZXIgQnkgRGF5JywNCiAgICAgICBjYXB0aW9uID0gcGFzdGUwKCdCYXNlZCBvbiBEYXRhIGZyb20gTWFyY2ggMDEsIDIwMjAgLSAnLCBlbmREYXRlUHJpbnRWKSkgKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY29sb3JQYWwpDQppbnZpc2libGUoIGRldi5vZmYoKSApICMgdG8gc3VwcHJlc3MgdGhlIHVud2FudGVkIG91dHB1dCBmcm9tIGRldi5vZmYNCg0KIyBQcmludGluZyBhIHBuZyB2ZXJzaW9uIG9mIHRoZSBwbG90IGluIE1hcmtkb3duIChsb3dlciBxdWFsaXR5IGltYWdlIGZvciBxdWlja2VyIGNvbXBpbGF0aW9uIG9mIEhUTUwpDQpyZWFkVElGRigiLi4vRmlndXJlcy9zdW1tYXJ5UGxvdC50aWZmIikgJT4lIGdyaWQucmFzdGVyKCkNCmBgYA0KDQojIyMgQ2x1c3RlciBNYXANCg0KYGBge3IgY2x1c3Rlck1hcCwgb3V0LndpZHRoPScxMDAlJ30NCiMgSm9pbmluZyB0aGUgY2x1c3RlckNvdW50aWVzIHJlc3VsdHMgd2l0aCB0aGUgZXhpc3RpbmcgY291bnR5IHNpbXBsZSBmZWF0dXJlcyBvYmplY3QgKGN0eV9zZikNCmNsdXN0ZXJDb3VudGllcyRmaXBzID0gc3RyX3BhZChjbHVzdGVyQ291bnRpZXMka2V5X251bWVyaWMsIHdpZHRoID0gNSwgc2lkZSA9ICdsZWZ0JywgcGFkID0gJzAnKQ0KY2x1c3RlckNvdW50aWVzICU8PiUgdW5ncm91cCgpDQpjdHlfc2YgJTw+JSBsZWZ0X2pvaW4oY2x1c3RlckNvdW50aWVzWywgYygnZmlwcycsICdjbHVzdGVyX2dyb3VwJyldLCBieSA9ICdmaXBzJykgIyBhZGRpbmcgY2x1c3Rlcl9ncm91cCB0byBjdHlfc2YNCg0KIyBDcmVhdGluZyBhIHN0YXRpYyB2aXN1YWwgZm9yIHRoZSBwYXBlcg0KdGlmZihmaWxlbmFtZSA9ICcuLi9GaWd1cmVzL2NsdXN0ZXJNYXAudGlmZicsIHdpZHRoID0gMTM2NiwgaGVpZ2h0ID03NjgsIHBvaW50c2l6ZSA9IDE2KQ0KdG1fc2hhcGUoY3R5X3NmKSArIHRtX3BvbHlnb25zKCdjbHVzdGVyX2dyb3VwJywgdGl0bGUgPSAnQ2x1c3RlciAjJywgcGFsZXR0ZSA9IGNvbG9yUGFsKSArDQogIHRtX2NyZWRpdHMocGFzdGUwKCdCYXNlZCBvbiBEYXRhIGZyb20gTWFyY2ggMDEsIDIwMjAgLSAnLCBlbmREYXRlUHJpbnRWKSwgcG9zaXRpb249YygicmlnaHQiLCAiYm90dG9tIikpDQppbnZpc2libGUoIGRldi5vZmYoKSApICMgdG8gc3VwcHJlc3MgdGhlIHVud2FudGVkIG91dHB1dCBmcm9tIGRldi5vZmYNCg0KDQojIENyZWF0aW5nIGFuIGludGVyYWN0aXZlIHZpc3VhbCBVc2luZyB0aGUgTGVhZmxldCBQYWNrYWdlDQojIyMjIENyZWF0aW5nIGEgbG9uZ2xhdCBwcm9qZWN0aW9uIChyZXF1aXJlZCBieSBsZWFmbGV0KQ0KbGVhZmxldF9zZiA9IGNvdW50aWVzX3NmKCJsb25nbGF0IikgJT4lIGZpbHRlcighc3RhdGUgJWluJSBjKCdBbGFza2EnLCAnSGF3YWlpJykpICMgZnJvbSBhbGJlcnN1YQ0KbGVhZmxldF9zZiAlPD4lIGdlb19qb2luKGNyb3NzU2VjdGlvbmFsRGF0YSwgYnlfc3A9ICdmaXBzJywgYnlfZGY9ICdmaXBzJykgJT4lIA0KICBsZWZ0X2pvaW4oY2x1c3RlckNvdW50aWVzWywgYygnZmlwcycsICdjbHVzdGVyX2dyb3VwJyldLCBieSA9ICdmaXBzJykNCg0KIyMjIyBTZXR0aW5nIHRoZSBDb2xvciBTY2hlbWUNCmxlYWZsZXRQYWwgPSAgY29sb3JGYWN0b3IoJ1NldDInLCBkb21haW4gPSBsZWFmbGV0X3NmJGNsdXN0ZXJfZ3JvdXAsIG5hLmNvbG9yID0gIndoaXRlIikNCg0KIyMjIyBUaGUgdmlzdWFsDQpsZWFmbGV0KGhlaWdodD01MDApICU+JSAjIGluaXRpYWxpemluZyB0aGUgbGVhZmxldCBtYXANCiAgc2V0VmlldyhsbmcgPSAtOTYsIGxhdCA9IDM3LjgsIHpvb20gPSAzLjgpICU+JSAjIHNldHRpbmcgdGhlIHZpZXcgb24gQ29udGluZW50YWwgVVMNCiAgYWRkVGlsZXMoKSAlPiUgIyBhZGRpbmcgdGhlIGRlZmF1bHQgdGlsZXMNCiAgYWRkUG9seWdvbnMoZGF0YSA9IGxlYWZsZXRfc2YsIHN0cm9rZSA9IEZBTFNFLCBmaWxsQ29sb3IgPSB+bGVhZmxldFBhbChsZWFmbGV0X3NmJGNsdXN0ZXJfZ3JvdXApLCAjIGFkZGluZyB0aGUgZGF0YQ0KICAgICAgICAgICAgICB3ZWlnaHQgPSAyLCBvcGFjaXR5ID0gMSwgY29sb3IgPSAid2hpdGUiLCBkYXNoQXJyYXkgPSAiMyIsIGZpbGxPcGFjaXR5ID0gMC43LCAjIGFkZGluZyBjb2xvciBzcGVjcw0KICAgICAgICAgICAgICBwb3B1cCA9IHBhc3RlKCJDb3VudHk6IiwgbGVhZmxldF9zZiRuYW1lLCAnPGJyPicsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDbHVzdGVyICM6IiwgbGVhZmxldF9zZiRjbHVzdGVyX2dyb3VwLCAnPGJyPicsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIlBvcHVsYXRpb24gRGVuc2l0eToiLCByb3VuZChsZWFmbGV0X3NmJHBvcERlbnNpdHksIDEpLCAnPGJyPicpKSAlPiUgI3BvcC11cCBNZW51DQogIGFkZExlZ2VuZChwb3NpdGlvbiA9ICJib3R0b21sZWZ0IiwgcGFsID0gbGVhZmxldFBhbCwgdmFsdWVzID0gIGxlYWZsZXRfc2YkY2x1c3Rlcl9ncm91cCwgDQogICAgICAgICAgICB0aXRsZSA9ICJDbHVzdGVyICMiLCBvcGFjaXR5ID0gMSkgIyBsZWdlbmQgZm9ybWF0dGluZw0KYGBgDQoNCg0KIyBFeHBsYW5hdG9yeSBNb2RlbGluZyBvZiBDbHVzdGVyIEFzc2lnbm1lbnRzDQoNCkluIHRoZSBwcmV2aW91cyBzZWN0aW9uLCB3ZSBzaG93ZWQgdGhhdCBieSB1c2luZyBzb2xlbHkgYSBzY2FsZWQgYW5kIHNtb290aGVkIHRpbWUgc2VyaWVzIG9mIGRhaWx5IGNhc2VzIHBlciBjb3VudHksIHRoZSBjb3VudGllcyBhcmUgZ3JvdXBlZCBpbnRvIGByIGxldmVscyhsZWFmbGV0X3NmJGNsdXN0ZXJfZ3JvdXApYCBjYXRlZ29yaWVzICh3aG9zZSB0aW1lLXNlcmllcyBoYXZlIGRpc3RpbmN0IHNoYXBlcyBiYXNlZCBvbiB0aGUgRXVjbGlkZWFuIGRpc3RhbmNlIG1lYXN1cmUpLiBJbiB0aGlzIHNlY3Rpb24sIHdlIGF0dGVtcHQgdG8gbW9kZWwgdGhlIGZhY3RvcnMgdGhhdCBhcmUgYXNzb2NpYXRlZCB3aXRoIHRoZSBjbHVzdGVyIGFzc2lnbm1lbnQuDQoNCg0KIyMgRGVzY3JpcHRpdmUgU3RhdGlzdGljcw0KDQpgYGB7ciBza2ltbWVkfQ0KbXVsdGlDbGFzc0RGID0gc2VsZWN0KGNsdXN0ZXJDb3VudGllcywgaWQsIGNsdXN0ZXJfZ3JvdXApICU+JSANCiAgbGVmdF9qb2luKGNyb3NzU2VjdGlvbmFsRGF0YSwgYnkgPSAnaWQnKSAgJT4lIA0KICBzZWxlY3QoLWMoYWRtaW5pc3RyYXRpdmVfYXJlYV9sZXZlbF8yLCBhZG1pbmlzdHJhdGl2ZV9hcmVhX2xldmVsXzMsIGlkLCBrZXlfbnVtZXJpYykpDQoNCnNhdmVSRFMobXVsdGlDbGFzc0RGLCAnLi4vRGF0YS9tdWx0aUNsYXNzREYucmRzJykgIyBzYXZpbmcgdGhlIGRhdGENCg0Kc2tpbShtdWx0aUNsYXNzREYpICMgcHJpbnRpbmcgYSBuaWNlIHN1bW1hcnkgdGFibGUgb2YgdGhlIGRhdGENCmBgYA0KDQoNCiMjIEJveHBsb3QgQnkgQ2x1c3Rlcg0KYGBge3IgYm94cGxvdENsdXN0ZXJ9DQptdWx0aUNsYXNzREYgJT4lIHBsb3RfYm94cGxvdChieSA9ICdjbHVzdGVyX2dyb3VwJywgbmNvbCA9IDJMLCANCiAgICAgICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9idygpLA0KICAgICAgICAgICAgICAgZ2VvbV9ib3hwbG90X2FyZ3MgPSBsaXN0KCdvdXRsaWVyLnNoYXBlJyA9IDEpKQ0KYGBgDQoNCg0KIyMgRXhwbGFuYXRvcnkgTW9kZWxpbmcgVXNpbmcgTXVsdGlub21pYWwgU3BhdGlhbCBSZWdyZXNzaW9uDQoNCiMjIyBEYXRhIFByZXBhcmF0aW9uDQoNCmBgYHtyIG11bHRpU3BhdGlhbH0NCm11bHRpQ2xhc3NERiRjbHVzdGVyX2dyb3VwICU8PiUgYXMuZmFjdG9yKCkgIyBjb252ZXJ0IHRvIGEgZmFjdG9yDQoNCiMgaW1wdXRlIHdpdGhvdXQgdXNpbmcgY2x1c3Rlcl9ncm91cCwga2V5X2dvb2dsZV9tb2JpbGl0eSBhbmQgZmlwcw0KbXVsdGlDbGFzc0ltcHV0ZWQgPSBWSU06OmtOTihtdWx0aUNsYXNzREYsIGltcF92YXIgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGlzdF92YXIgPSBjb2xuYW1lcyhtdWx0aUNsYXNzREYpWzM6MTBdKQ0Kc2F2ZVJEUyhtdWx0aUNsYXNzSW1wdXRlZCwgJy4uL0RhdGEvbXVsdGlDbGFzc0ltcHV0ZWQucmRzJykgIyBzYXZpbmcgdGhlIGRhdGENCg0KIyBDcmVhdGluZyBhIGRmICh3aGljaCB3aWxsIGJlIHVzZWQgZm9yIGFuYWx5c2lzKQ0KZGYgPSBtdWx0aUNsYXNzSW1wdXRlZCAjIHNldHRpbmcgZGYgdG8gZXF1YWwgdG8gdGhlIG11bHRpY2xhc3Mgb2JqZWN0DQpkZiRjbHVzdFJlTGV2ZWxlZCA9ICByZWxldmVsKGRmJGNsdXN0ZXJfZ3JvdXAsIHJlZiA9IG1heENhdChkZiRjbHVzdGVyX2dyb3VwKSApICMgc2V0dGluZyB0aGUgcmVmIGxldmVsDQpkZiAgPSBkZiAlPiUgc2VsZWN0KC1jKGNsdXN0ZXJfZ3JvdXAsICMgcmVtb3ZlZCBzaW5jZSBpdCBpcyBub3cgcmVkdW5kYW50IHdpdGggdGhlIGNsdXN0UmVMZXZlbGVkIHZhcmlhYmxlDQogICAgICAgICAgICAgICAgICAgICAgIGtleV9nb29nbGVfbW9iaWxpdHksICMgcmVtb3ZlZCBzaW5jZSB0aGV5IGFyZSBpZGVudGlmaWVyIHZhcmlhYmxlcw0KICAgICAgICAgICAgICAgICAgICAgICBpY3VCZWRzUGVyMTAwMDBTZW5pb3JzLCBwZXJjUmVwVm90ZXMpKSAjIGRpZCBub3Qgc2lnLiBpbXByb3ZlIHByZWRpY3Rpb25zDQpzYXZlUkRTKGRmLCAnLi4vRGF0YS9kZi5yZHMnKSAjIHNhdmluZyB0aGUgZGF0YQ0KYGBgDQoNCiMjIyBNb2RlbCBCdWlsZGluZw0KDQpgYGB7ciBzcGF0aWFsTW9kZWxCdWlsZH0NCm11bHRpQ2xhc3NERi5zb3J0ZWQgPSBkZltvcmRlcihkZiRmaXBzKSwgXQ0KDQojIEZpbGUgbmVlZHMgdG8gYmUgZG93bmxvYWRlZCBmcm9tIA0KIyBodHRwczovL3d3dzIuY2Vuc3VzLmdvdi9nZW8vdGlnZXIvVElHRVIyMDE2L0NPVU5UWS90bF8yMDE2X3VzX2NvdW50eS56aXANCnNoYXBlID0gIHJlYWRPR1IoJ0o6L015IERyaXZlL01pYW1pL0NvZGUvR2l0SHViL2NvdmlkL0RhdGEnLCAidGxfMjAxNl91c19jb3VudHkiKQ0Kc2hhcGVfc3AgPSBzdF9hc19zZihzaGFwZSkNCg0KIyBKb2luaW5nIGJvdGggdGhlIGNsdXN0ZXJlZCBkYXRhIGFuZCB0aGUgc2hhcGUgZmlsZSBpbiBvbmUgb2JqZWN0DQpqb2luZWRNdWx0aUNsYXNzID0gZ2VvX2pvaW4oc2hhcGVfc3AsIG11bHRpQ2xhc3NERi5zb3J0ZWQsIGJ5X3NwID0gJ0dFT0lEJywgYnlfZGYgPSdmaXBzJyxob3cgPSAnaW5uZXInKQ0Kam9pbmVkTXVsdGlDbGFzcy5zb3J0ZWQgPSBqb2luZWRNdWx0aUNsYXNzW29yZGVyKGpvaW5lZE11bHRpQ2xhc3MkR0VPSUQpLCBdDQoNCiMgQnVpbGRpbmcgYSBuZWlnaGJvcmhvb2QgbGlzdCBmcm9tIHRoZSBzaGFwZSBkYXRhDQpuYkxpc3QgPSBwb2x5Mm5iKGpvaW5lZE11bHRpQ2xhc3Muc29ydGVkKSAlPiUgDQogIG5iMmxpc3R3KHN0eWxlID0gJ1cnLCB6ZXJvLnBvbGljeSA9IFRSVUUpICMgY29udmVydGluZyBpdCB0byBhIGxpc3R3IG9iamVjdA0KDQojIElkZW50aWZ5aW5nIHRoZSBudW1iZXIgb2YgbmVpZ2hib3JzIHBlciBjb3VudHkNCm51bUNvdW50aWVzID0gbGVuZ3RoKG5iTGlzdCRuZWlnaGJvdXJzKSAjIG51bWJlciBvZiBjb3VudGllcw0KbnVtTmVpZ2hib3JzID0gcmVwKDAsIG51bUNvdW50aWVzKSAjIG51bWJlciBvZiBuZWlnaGJvcnMgZm9yIGVhY2ggY291bnR5DQpmb3IgKGkgaW4gMTpudW1Db3VudGllcykgIG51bU5laWdoYm9yc1tpXSA9IGxlbmd0aChuYkxpc3QkbmVpZ2hib3Vyc1tbaV1dKQ0KDQojIElkZW50aWZ5aW5nIHRoZSBmaXBzIG9mIHRoZSBuZWlnaGJvciBjb3VudGllcw0KZmlwc09mTmVpZ2hib3VyQ291bnRpZXMgPSBjKCkgIyBpbml0aWFsaXphdGlvbg0KZm9yIChpIGluIDE6bnVtQ291bnRpZXMpIGZpcHNPZk5laWdoYm91ckNvdW50aWVzID0gYyhmaXBzT2ZOZWlnaGJvdXJDb3VudGllcywgbmJMaXN0JG5laWdoYm91cnNbW2ldXSApDQoNCiMgUHJlLW5pbWJsZSBwYXJhbWV0ZXJzDQpzdW1OdW1OZWlnaCA9IGxlbmd0aChmaXBzT2ZOZWlnaGJvdXJDb3VudGllcykNCm0gPSBsZW5ndGgobnVtTmVpZ2hib3JzKQ0KDQojIFByZXBhcmluZyB0aGUgb3V0Y29tZQ0KbnVtYmVyX29mX2NsdXN0ZXJzID0gdW5pcXVlKG11bHRpQ2xhc3NERi5zb3J0ZWQkY2x1c3RSZUxldmVsZWQpICU+JSBsZW5ndGgoKQ0KY2x1c3Rlcl9ncm91cF9tYXRyaXggPSBtYXRyaXgoIDAgLCBucm93ID0gbSwgbmNvbCA9IG51bWJlcl9vZl9jbHVzdGVycykNCmNsdXN0ZXJfZ3JvdXBfbnUgPSBzdHJfcmVtb3ZlKG11bHRpQ2xhc3NERi5zb3J0ZWQkY2x1c3RSZUxldmVsZWQsICdDJykgJT4lIGFzLm51bWVyaWMoKQ0KDQpmb3IgKGkgaW4gMTptKQ0Kew0KICBjbHVzdGVyX2dyb3VwX21hdHJpeFsgaSAsIGNsdXN0ZXJfZ3JvdXBfbnVbaV0gXSA9IDENCn0NCg0KIyMgU3RvcmluZyBlYWNoIHByZWRpY3RvciBpbiBhIHZlY3Rvcg0KY291bnR5VHlwZSA9IG11bHRpQ2xhc3NERi5zb3J0ZWQkY291bnR5VHlwZQ0KVW5kZXJzZXJ2ZWQgPSBmYXN0RHVtbWllczo6ZHVtbXlfY29scyhjb3VudHlUeXBlKQ0KVW5kZXJzZXJ2ZWQucnVyYWwgPSBVbmRlcnNlcnZlZCRgLmRhdGFfUnVyYWwvVW5kZXJzZXJ2ZWRgICU+JSBhcy5tYXRyaXgoKSAlPiUgYXMudmVjdG9yKCkNCnBvcERlbnNpdHkgPSBtdWx0aUNsYXNzREYuc29ydGVkJHBvcERlbnNpdHkgJT4lIGFzLm1hdHJpeCgpICU+JSBhcy52ZWN0b3IoKQ0KR292ZXJubWVudFJlc3BvbnNlSW5kZXhNZWRpYW4gPSBtdWx0aUNsYXNzREYuc29ydGVkJEdvdmVybm1lbnRSZXNwb25zZUluZGV4TWVkaWFuICU+JSBhcy5tYXRyaXgoKSAlPiUgYXMudmVjdG9yKCkNClBlcmNlbnRTZW5pb3JzID0gbXVsdGlDbGFzc0RGLnNvcnRlZCRQZXJjZW50U2VuaW9ycyAlPiUgYXMubWF0cml4KCkgJT4lIGFzLnZlY3RvcigpDQpwb3ZlcnR5UGVyY2VudCA9IG11bHRpQ2xhc3NERi5zb3J0ZWQkcG92ZXJ0eVBlcmNlbnQgJT4lIGFzLm1hdHJpeCgpICU+JSBhcy52ZWN0b3IoKQ0KcmVnaW9ucyA9IG11bHRpQ2xhc3NERi5zb3J0ZWQkcmVnaW9ucw0KDQpyZXN1bHRzID0gZHVtbXlfY29scyhyZWdpb25zKQ0KQSA9IHJlc3VsdHMkLmRhdGFfQQ0KQiA9IHJlc3VsdHMkLmRhdGFfQg0KQyA9IHJlc3VsdHMkLmRhdGFfQw0KRCA9IHJlc3VsdHMkLmRhdGFfRA0KRSA9IHJlc3VsdHMkLmRhdGFfRQ0KRkYgPSByZXN1bHRzJC5kYXRhX0YNCkcgPSByZXN1bHRzJC5kYXRhX0cNCkggPSByZXN1bHRzJC5kYXRhX0gNCkkgPSByZXN1bHRzJC5kYXRhX0kNCg0KDQoNCiMgTmltYmxlIENvZGUgc3BhdGlhbA0KY29kZSA9IG5pbWJsZUNvZGUoDQogIHsNCiAgICBmb3IgKGkgaW4gMTptKQ0KICAgIHsNCiAgICAgIGNsdXN0ZXJfZ3JvdXBfbWF0cml4W2ksMTpudW1iZXJfb2ZfY2x1c3RlcnNdIH4gZG11bHRpKCBwcm9iID0gcFtpLDE6bnVtYmVyX29mX2NsdXN0ZXJzXSAsMSkgDQogICAgICBwaGlbaSwxXSA8LSAxDQogICAgICBwW2ksMV0gPC0gMS9zdW0ocGhpW2ksMTpudW1iZXJfb2ZfY2x1c3RlcnNdKQ0KICAgICAgZm9yIChrIGluIDI6bnVtYmVyX29mX2NsdXN0ZXJzKQ0KICAgICAgew0KICAgICAgICBsb2cocGhpW2ksa10pIDwtIGIwW2tdICsgYjFba10qVW5kZXJzZXJ2ZWQucnVyYWxbaV0gKyANCiAgICAgICAgICBiMltrXSpwb3BEZW5zaXR5W2ldICsNCiAgICAgICAgICBiM1trXSpQZXJjZW50U2VuaW9yc1tpXSArIA0KICAgICAgICAgIGI0W2tdKkdvdmVybm1lbnRSZXNwb25zZUluZGV4TWVkaWFuW2ldICsgDQogICAgICAgICAgYjVba10qcG92ZXJ0eVBlcmNlbnRbaV0gKyBiNltrXSpBW2ldICsgYjdba10qQltpXSArIA0KICAgICAgICAgIGI4W2tdKkNbaV0gKyBiOVtrXSpEW2ldICsgYjEwW2tdKkVbaV0gKyBiMTFba10qRltpXSArIGIxMltrXSpHW2ldICsgYjEzW2tdKkhbaV0gKyBiMTRba10qSVtpXSArIHVbaV0NCiAgICAgICAgcFtpLGtdIDwtIHBoaVtpLGtdL3N1bShwaGlbaSwxOm51bWJlcl9vZl9jbHVzdGVyc10pDQogICAgICB9DQogICAgfSAgICANCiAgICBmb3IgKGsgaW4gMjpudW1iZXJfb2ZfY2x1c3RlcnMpIA0KICAgIHsNCiAgICAgIGIwW2tdIH4gZG5vcm0oMCwgMC4wMDAwMSk7IGIxW2tdIH4gZG5vcm0oMCwgMC4wMDAwMSk7IGIyW2tdIH4gZG5vcm0oMCwgMC4wMDAwMSkNCiAgICAgIGIzW2tdIH4gZG5vcm0oMCwgMC4wMDAwMSk7IGI0W2tdIH4gZG5vcm0oMCwgMC4wMDAwMSk7IGI1W2tdIH4gZG5vcm0oMCwgMC4wMDAwMSkNCiAgICAgIGI2W2tdIH4gZG5vcm0oMCwgMC4wMDAwMSk7IGI3W2tdIH4gZG5vcm0oMCwgMC4wMDAwMSk7IGI4W2tdIH4gZG5vcm0oMCwgMC4wMDAwMSkNCiAgICAgIGI5W2tdIH4gZG5vcm0oMCwgMC4wMDAwMSk7IGIxMFtrXSB+IGRub3JtKDAsIDAuMDAwMDEpOyBiMTFba10gfiBkbm9ybSgwLCAwLjAwMDAxKQ0KICAgICAgYjEyW2tdIH4gZG5vcm0oMCwgMC4wMDAwMSk7IGIxM1trXSB+IGRub3JtKDAsIDAuMDAwMDEpOyBiMTRba10gfiBkbm9ybSgwLCAwLjAwMDAxKQ0KICAgIH0NCiAgICB1WzE6bV0gfiBkY2FyX25vcm1hbChhZGpbMTpzdW1OdW1OZWlnaF0sIHdlaWdodHNbMTpzdW1OdW1OZWlnaF0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgIG51bVsxOm1dLHRhdXUpDQogICAgZm9yIChqIGluIDE6c3VtTnVtTmVpZ2gpDQogICAge3dlaWdodHNbal0gPC0gMX0NCiAgICB0YXV1IH4gZGdhbW1hKDEsMC4wMDAxKQ0KICB9DQopICANCg0KY29uc3RhbnRzID0gbGlzdChudW09bnVtTmVpZ2hib3JzLCBhZGo9Zmlwc09mTmVpZ2hib3VyQ291bnRpZXMsDQogICAgICAgICAgICAgICAgICBzdW1OdW1OZWlnaCA9IGxlbmd0aChmaXBzT2ZOZWlnaGJvdXJDb3VudGllcyksIA0KICAgICAgICAgICAgICAgICAgbT1tLG51bWJlcl9vZl9jbHVzdGVycz1udW1iZXJfb2ZfY2x1c3RlcnMpDQoNCmRhdGEgPSBsaXN0KGNsdXN0ZXJfZ3JvdXBfbWF0cml4ID0gY2x1c3Rlcl9ncm91cF9tYXRyaXgsIA0KICAgICAgICAgICAgIFVuZGVyc2VydmVkLnJ1cmFsID0gVW5kZXJzZXJ2ZWQucnVyYWwsDQogICAgICAgICAgICAgcG9wRGVuc2l0eSA9IHBvcERlbnNpdHksDQogICAgICAgICAgICAgUGVyY2VudFNlbmlvcnMgPSBQZXJjZW50U2VuaW9ycywNCiAgICAgICAgICAgICBHb3Zlcm5tZW50UmVzcG9uc2VJbmRleE1lZGlhbiA9IEdvdmVybm1lbnRSZXNwb25zZUluZGV4TWVkaWFuLA0KICAgICAgICAgICAgIHBvdmVydHlQZXJjZW50ID0gcG92ZXJ0eVBlcmNlbnQsDQogICAgICAgICAgICAgQT1BLCBCPUIsIEM9QywgRD1ELCBFPUUsIEY9RkYsDQogICAgICAgICAgICAgRz1HLCBIPUgsIEk9SSkNCg0KaW5pdHMgPSBsaXN0KGIwPXJlcCgwLCBudW1iZXJfb2ZfY2x1c3RlcnMpLCB1PXJlcCgwLG0pLHRhdXU9MSwgYjE9cmVwKDAsIG51bWJlcl9vZl9jbHVzdGVycyksIA0KICAgICAgICAgICAgIGIyPXJlcCgwLCBudW1iZXJfb2ZfY2x1c3RlcnMpLCBiMz1yZXAoMCwgbnVtYmVyX29mX2NsdXN0ZXJzKSwgYjQ9cmVwKDAsIG51bWJlcl9vZl9jbHVzdGVycyksDQogICAgICAgICAgICAgYjU9cmVwKDAsIG51bWJlcl9vZl9jbHVzdGVycyksIGI2PXJlcCgwLCBudW1iZXJfb2ZfY2x1c3RlcnMpLCBiNz1yZXAoMCwgbnVtYmVyX29mX2NsdXN0ZXJzKSwgDQogICAgICAgICAgICAgYjg9cmVwKDAsIG51bWJlcl9vZl9jbHVzdGVycyksIGI5PXJlcCgwLCBudW1iZXJfb2ZfY2x1c3RlcnMpLCBiMTA9cmVwKDAsIG51bWJlcl9vZl9jbHVzdGVycyksDQogICAgICAgICAgICAgYjExPXJlcCgwLCBudW1iZXJfb2ZfY2x1c3RlcnMpLCBiMTI9cmVwKDAsIG51bWJlcl9vZl9jbHVzdGVycyksIGIxMz1yZXAoMCwgbnVtYmVyX29mX2NsdXN0ZXJzKSwNCiAgICAgICAgICAgICBiMTQ9cmVwKDAsIG51bWJlcl9vZl9jbHVzdGVycykgKQ0KDQpSbW9kZWwgPSBuaW1ibGVNb2RlbChjb2RlPWNvZGUsIGNvbnN0YW50cz1jb25zdGFudHMsIGRhdGE9ZGF0YSwgaW5pdHM9aW5pdHMpDQoNCmNvbXBpbGUuUm1vZGVsID0gY29tcGlsZU5pbWJsZSggUm1vZGVsICkNCg0KbW9uaXRvcnMgPSBjKCdiMCcsJ2IxJywnYjInLCdiMycsJ2I0JywnYjUnLCdiNicsJ2I3JywnYjgnLA0KICAgICAgICAgICAgICAnYjknLCdiMTAnLCdiMTEnLCdiMTInLCdiMTMnLCdiMTQnLCdwJywndGF1dScpDQoNClJtb2RlbC5Db25mID0gY29uZmlndXJlTUNNQyggUm1vZGVsICwgbW9uaXRvcnM9bW9uaXRvcnMsIHRoaW4gPSAxMDApDQoNClJtb2RlbC5NQ01DID0gYnVpbGRNQ01DKCBSbW9kZWwuQ29uZiApDQpjb21waWxlLlJtb2RlbC5NQ01DID0gY29tcGlsZU5pbWJsZSggUm1vZGVsLk1DTUMgKQ0KDQpuaXRlciA9IDMwMDAwMA0KbmJ1cm4gPSAxNTAwMDANCg0Kc3RhcnQudGltZSA9IHByb2MudGltZSgpDQpzcGF0aWFsLmJhc2U2ID0gcnVuTUNNQyggY29tcGlsZS5SbW9kZWwuTUNNQywgbml0ZXIgPSBuaXRlciwgbmJ1cm5pbiA9IG5idXJuLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGluaXRzID0gaW5pdHMsIG5jaGFpbnMgPSAxLCBzYW1wbGVzQXNDb2RhTUNNQyA9IFRSVUUgKQ0Kc3RvcC50aW1lID0gcHJvYy50aW1lKCkNCnRpbWUuZWxhcHNlZCA9IHN0b3AudGltZSAtIHN0YXJ0LnRpbWUNCnByaW50KCB0aW1lLmVsYXBzZWQgKQ0KYGBgDQoNCiMjIFJlc3VsdGluZyBNb2RlbA0KYGBge3Igc3BhdGlhbE1vZGVsQ29lZiwgcmVzdWx0cyA9ICdhc2lzJ30NCkJldGFjb2UgPSBzcGF0aWFsLmJhc2U2WywgKDE6NjApXSAjIGJhc2VkIG9uIGN1cnJlbnQgbnVtYmVyIG9mIHByZWRpY3RvcnMNCnNhdmVSRFMoQmV0YWNvZSwgJy4uL0RhdGEvYmV0YWNvZS5yZHMnKSAjIHNhdmluZyB0aGUgZGF0YQ0KDQojIENvbXB1dGluZyB0aGUgY29lZmZpY2llbnRzJyB2YWx1ZXMNCmNvZWZmVGFibGUgPSByYmluZChjb2xNZWFucyhCZXRhY29lKSwgY29sU2RzKEJldGFjb2UpKQ0Kcm93bmFtZXMoY29lZmZUYWJsZSkgPSBjKCdtZWFucycsICdzdGRldnMnKQ0KDQojIEZvcm1hdGluZyB0aGUgb3V0cHV0DQpjb2VmZlRhYmxlICU8PiUgYXMuZGF0YS5mcmFtZSgpICU+JSANCiAgc2VsZWN0KHBhc3RlMChwYXN0ZTAoImIiLCByZXAoc2VxKDAsIDE0KSwgNCkpLCAnWycsIA0KICAgICAgICAgICAgICAgIGMocmVwKDEsMTUpLCByZXAoMiwxNSksIHJlcCgzLDE1KSwgcmVwKDQsMTUpKSwNCiAgICAgICAgICAgICAgICAnXScgKSkgJT4lICMgcmVvcmRlcmluZyBjb2xzIGJ5IG5hbWUNCiAgc2VsZWN0X2lmKH4gIWFueShpcy5uYSguKSkpICAjIGRyb3BwaW5nIE5BIGNvbHMgKGNvcnJlc3BvbmRpbmcgdG8gQ2x1c3RlciAxKQ0KDQp0Q29lZmZUYWJsZSA9IHQoY29lZmZUYWJsZSkNCg0KdENvZWZmVGFibGUgPSBjYmluZCh0Q29lZmZUYWJsZVsxOjE1LF0sIHRDb2VmZlRhYmxlWzE2OjMwLF0sIHRDb2VmZlRhYmxlWzMxOjQ1LF0pICU+JSBkYXRhLmZyYW1lKCkNCnJvdy5uYW1lcyh0Q29lZmZUYWJsZSkgPSBjKCdjb25zdGFudCcsICdydXJhbCcsICdwb3BEZW5zaXR5JywgJ3BlcmNTZW5pb3JzJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICdnb3ZSZXNwb25zZScsICdwZXJjUG92ZXJ0eScsICdyZWdpb25BJywgJ3JlZ2lvbkInLCAncmVnaW9uQycsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAncmVnaW9uRCcsICdyZWdpb25FJywgJ3JlZ2lvbkYnLCAncmVnaW9uRycsICdyZWdpb25IJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICdyZWdpb25JJykNCmNvbG5hbWVzKHRDb2VmZlRhYmxlKSA9IGMoJ0MyX2NvZWZfbWVhbicsICdDMl9jb2VmX3NkJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgJ0MzX2NvZWZfbWVhbicsICdDM19jb2VmX3NkJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgJ0M0X2NvZWZfbWVhbicsICdDNF9jb2VmX3NkJykNCg0KdENvZWZmVGFibGUgJT4lIHJvdW5kKGRpZ2l0cyA9IDMpICU+JSBkYXRhdGFibGUoDQogICAgICAgICAgZXh0ZW5zaW9ucyA9IGMoJ0ZpeGVkQ29sdW1ucycsICdCdXR0b25zJyksIG9wdGlvbnMgPSBsaXN0KA0KICAgICAgICAgICAgcGFnZUxlbmd0aCA9IDE1LA0KICAgICAgICAgICAgZG9tID0gJ0JmcnRpcCcsDQogICAgICAgICAgICBzY3JvbGxYID0gVFJVRSwNCiAgICAgICAgICAgIGJ1dHRvbnMgPSBjKCdjb3B5JywgJ2NzdicsICdleGNlbCcsICdwZGYnKSwNCiAgICAgICAgICAgIGZpeGVkQ29sdW1ucyA9IGxpc3QobGVmdENvbHVtbnMgPSAxKSkpDQpgYGANCg0KIyMgTW9kZWwncyBQZXJmb3JtYW5jZQ0KDQpgYGB7ciBzcGF0aWFsTW9kZWxSZXN1bHRzfQ0Kc2FtcGxlc19wID0gc3BhdGlhbC5iYXNlNlssIC0oMTo2MCldDQpzYW1wbGVzX3BfbWVhbiA9IGNvbE1lYW5zKGV4cChzYW1wbGVzX3BbLCAxOihudW1iZXJfb2ZfY2x1c3RlcnMqbSldKSkNCkMxID0gc2FtcGxlc19wX21lYW5bMTozMTA4XQ0KQzIgPSBzYW1wbGVzX3BfbWVhblszMTA5OjYyMTZdDQpDMyA9IHNhbXBsZXNfcF9tZWFuWzYyMTc6OTMyNF0NCkM0ID0gc2FtcGxlc19wX21lYW5bOTMyNToxMjQzMl0NCnByZWQuMCA9IGNiaW5kKEMxLCBDMiwgQzMsIEM0KSANCnByZWQgPSByZXAoTkEsIG0pDQpmb3IgKGkgaW4gMTptKSB7DQogIHZlYyA9IHByZWQuMFtpLCAxOm51bWJlcl9vZl9jbHVzdGVyc10NCiAgcHJlZFtpXSA9IHdoaWNoLm1heCh2ZWMpDQp9DQoNCnByZWRpY3RlZC5zcGF0aWFsID0gY2JpbmQocHJlZCwgbXVsdGlDbGFzc0RGLnNvcnRlZFssICdmaXBzJ10pIA0KY29sbmFtZXMocHJlZGljdGVkLnNwYXRpYWwpID0gYygncHJlZCcsICdmaXBzJykNCg0KcHJlZFNwYXRpYWxGaW5hbCA9IG1lcmdlKHByZWRpY3RlZC5zcGF0aWFsLCBtdWx0aUNsYXNzREYuc29ydGVkWywgYygnY2x1c3RSZUxldmVsZWQnLCAnZmlwcycpXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBieSA9ICdmaXBzJykNCg0KcHJlZFNwYXRpYWxGaW5hbCRjbHVzdFJlTGV2ZWxlZCAlPD4lICBzdHJfcmVtb3ZlKCdDJykNCnNhdmVSRFMocHJlZFNwYXRpYWxGaW5hbCwgJy4uL0RhdGEvcHJlZFNwYXRpYWxGaW5hbC5yZHMnKSAjIHNhdmluZyB0aGUgZGF0YQ0KDQoNCiMgQ29tcHV0aW5nIHRoZSBDb25mdXNpb24gTWV0cmljcyBhbmQgQnkgQ2xhc3MgTWV0cmljcw0KY29uZk1hdHJpeCA9IGNvbmZ1c2lvbk1hdHJpeChhcy5mYWN0b3IocHJlZFNwYXRpYWxGaW5hbCRwcmVkKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzLmZhY3RvcihwcmVkU3BhdGlhbEZpbmFsJGNsdXN0UmVMZXZlbGVkKSkNCnNhdmVSRFMoY29uZk1hdHJpeCwgJy4uL0RhdGEvY29uZk1hdHJpeFNwYXRpYWxNb2RlbC5yZHMnKSAjIHNhdmluZyB0aGUgZGF0YQ0KDQojIFByaW50aW5nIHRoZSBSZXN1bHRpbmcgdGFibGVzIG5pY2VseQ0KcGFuZGVyKGNvbmZNYXRyaXgkdGFibGUpDQpwYW5kZXIoY29uZk1hdHJpeCRieUNsYXNzKQ0KcGFuZGVyKGNvbmZNYXRyaXgkb3ZlcmFsbCkNCmBgYA0KDQoNCg0KDQojIyBWaXN1YWxpemluZyB0aGUgTW9kZWwncyBPdXRjb21lcw0KYGBge3Igdml6U3BhdGlhbE1vZGVsfQ0KIyMjVmlzdWFsaXppbmcgdGhlIE1vZGVs4oCZcyBQcmVkaWN0aW9ucw0KcHJlZFNwYXRpYWxGaW5hbCA9IHJlYWRSRFMoJy4uL0RhdGEvcHJlZFNwYXRpYWxGaW5hbC5yZHMnKSAjIHNhdmluZyB0aGUgZGF0YQ0KcHJlZFNwYXRpYWxGaW5hbCRtYXRjaCA9IGlmZWxzZShwcmVkU3BhdGlhbEZpbmFsJHByZWQgPT0gcHJlZFNwYXRpYWxGaW5hbCRjbHVzdFJlTGV2ZWxlZCwgIlllcyIsICJObyIpICU+JQ0KICBhcy5mYWN0b3IoKQ0KDQptYXBSZXN1bHRzID0gcHJlZFNwYXRpYWxGaW5hbA0KbWFwUmVzdWx0cyRmaXBzICU8PiUgYXMuZmFjdG9yKCkNCg0KIyBSZXRyaWV2aW5nIHRoZSBVLlMuIGNvdW50eSBjb21wb3NpdGUgbWFwIGFzIGEgc2ltcGxlZmVhdHVyZSAoc2luY2UgaXQgaGFzIGJlZW4gb3ZlcndyaXR0ZW4pDQpjb3VudGllc19zZiA9IGNvdW50aWVzX3NmKCJhZXFkIikgJT4lIGZpbHRlcighc3RhdGUgJWluJSBjKCdBbGFza2EnLCAnSGF3YWlpJykpICMgZnJvbSBhbGJlcnN1YQ0KY291bnRpZXNfc2YgJTw+JSBnZW9fam9pbihtYXBSZXN1bHRzLCBieV9zcD0gJ2ZpcHMnLCBieV9kZj0gJ2ZpcHMnKQ0KDQoNCiMgQ3JlYXRpbmcgYSBzdGF0aWMgdmlzdWFsIGZvciB1c2UgaW4gdGhlIHBhcGVyDQp0aWZmKGZpbGVuYW1lID0gJy4uL0ZpZ3VyZXMvY2x1c3Rlck1hdGNoTWFwU3BhdGlhbC50aWZmJywgd2lkdGggPSAxMzY2LCBoZWlnaHQgPTc2OCwgcG9pbnRzaXplID0gMTYpDQp0bV9zaGFwZShjb3VudGllc19zZikgKyB0bV9wb2x5Z29ucygnbWF0Y2gnLCB0aXRsZSA9ICdDbHVzdGVyIE1hdGNoJywgc3R5bGUgPSAnY29udCcsIHBhbGV0dGUgPSAiZGl2IikgKw0KICB0bV9sYXlvdXQoYWVzLnBhbGV0dGUgPSBsaXN0KGRpdiA9IGxpc3QoIlllcyIgPSAiI0NBQjJENiIsICJObyIgPSAiIzZBM0Q5QSIpKSkgKw0KICB0bV9jcmVkaXRzKHBhc3RlMCgnQmFzZWQgb24gRGF0YSBmcm9tIE1hcmNoIDAxLCAyMDIwIC0gJywgZW5kRGF0ZVByaW50ViksIHBvc2l0aW9uPWMoInJpZ2h0IiwgImJvdHRvbSIpKQ0KaW52aXNpYmxlKCBkZXYub2ZmKCkgKSAjIHRvIHN1cHByZXNzIHRoZSB1bndhbnRlZCBvdXRwdXQgZnJvbSBkZXYub2ZmDQoNCg0KIyBDcmVhdGluZyBhIGxvbmdsYXQgcHJvamVjdGlvbiAocmVxdWlyZWQgYnkgbGVhZmxldCkNCmxlYWZsZXRfc2YgPSBjb3VudGllc19zZigibG9uZ2xhdCIpICU+JSBmaWx0ZXIoIXN0YXRlICVpbiUgYygnQWxhc2thJywgJ0hhd2FpaScpKSAjIGZyb20gYWxiZXJzdWENCmxlYWZsZXRfc2YgJTw+JSBnZW9fam9pbihtYXBSZXN1bHRzLCBieV9zcD0gJ2ZpcHMnLCBieV9kZj0gJ2ZpcHMnKQ0KDQpsZWFmbGV0UGFsID0gY29sb3JGYWN0b3IocGFsZXR0ZSA9IGMoIiNDQUIyRDYiLCAiIzZBM0Q5QSIpLCBsZXZlbHMgPSBjKCdZZXMnLCAnTm8nKSwgbmEuY29sb3IgPSAid2hpdGUiKQ0KDQpsZWFmbGV0KGhlaWdodCA9IDUwMCkgJT4lIA0KICBzZXRWaWV3KGxuZyA9IC05NiwgbGF0ID0gMzcuOCwgem9vbSA9IDMuOCkgJT4lDQogIGFkZFRpbGVzKCkgJT4lDQogIGFkZFBvbHlnb25zKGRhdGEgPSBsZWFmbGV0X3NmLCBzdHJva2UgPSBGQUxTRSwgZmlsbENvbG9yID0gfmxlYWZsZXRQYWwobGVhZmxldF9zZiRtYXRjaCksDQogICAgICAgICAgICAgIHdlaWdodCA9IDIsIG9wYWNpdHkgPSAxLCBjb2xvciA9ICJ3aGl0ZSIsIGRhc2hBcnJheSA9ICIzIiwgZmlsbE9wYWNpdHkgPSAwLjcsDQogICAgICAgICAgICAgIHBvcHVwID0gcGFzdGUoIkNvdW50eToiLCBsZWFmbGV0X3NmJG5hbWUsICc8YnI+JywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNsdXN0ZXIgIzoiLCBsZWFmbGV0X3NmJGNsdXN0UmVMZXZlbGVkLCAnPGJyPicsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNsdXN0ZXIgUHJlZGljdGVkOiIsIGxlYWZsZXRfc2YkcHJlZCwgJzxicj4nLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDbHVzdGVyIE1hdGNoOiIsIGxlYWZsZXRfc2YkbWF0Y2gsICc8YnI+JykpICU+JSAjcG9wLXVwIE1lbnUNCiAgYWRkTGVnZW5kKHBvc2l0aW9uID0gImJvdHRvbWxlZnQiLCBwYWwgPSBsZWFmbGV0UGFsLCB2YWx1ZXMgPSAgbGVhZmxldF9zZiRtYXRjaCwgDQogICAgICAgICAgICB0aXRsZSA9ICJDbHVzdGVyIE1hdGNoIiwgb3BhY2l0eSA9IDEpDQpgYGANCg0KDQoNCi0tLQ0KDQojIFJlZmVyZW5jZXMNCjxkaXYgaWQ9InJlZnMiPjwvZGl2Pg0KDQotLS0NCg0KIyBBcHBlbmRpY2VzDQoNCiMjIEFwcGVuZGl4IEE6IFBhY2thZ2VzIFVzZWQNCkluIHRoZSBhcHBlbmRpeCwgd2UgcHJpbnQgYWxsIHRoZSBSIHBhY2thZ2VzIHVzZWQgaW4gb3VyIGFuYWx5c2lzIGFuZCB0aGVpciB2ZXJzaW9ucyB0byBhc3Npc3Qgd2l0aCByZXByb2R1Y2luZyBvdXIgcmVzdWx0cy9hbmFseXNpcy4NCg0KYGBge3Igc2Vzc2lvbkluZm99DQpwYW5kZXIoc2Vzc2lvbkluZm8oKSwgY29tcGFjdCA9IFRSVUUpICMgcHJpbnRpbmcgdGhlIHNlc3Npb24gaW5mb3JtYXRpb24NCmBgYA0KDQojIyBBcHBlbmRpeCBCOiBCYXNlbGluZSBNdWx0aW5vbWlhbCBNb2RlbA0KDQojIyMgTW9kZWwgQnVpbGRpbmcNCg0KYGBge3IgbXVsdGlNb2RlbH0NCmRmID0gcmVhZFJEUygnLi4vRGF0YS9kZi5yZHMnKSAjIGxvYWRpbmcgdGhlIGRhdGENCmRmICU8PiUgc2VsZWN0KC1maXBzKSAjIHJlbW92ZWQgc2luY2UgaXQgd2FzIG9ubHkgdXNlZCBpbiB0aGUgc3BhdGlhbCBtb2RlbCANCmZpbmFsTW9kZWwgPSAgcXVpZXQobXVsdGlub20oY2x1c3RSZUxldmVsZWQgfiAuLCBkYXRhID0gZGYpKSAjIGJ1aWxkaW5nIHRoZSBtdWx0aW5vbWlhbCBtb2RlbA0KYGBgDQoNCiMjIyBSZXN1bHRpbmcgTW9kZWwNCmBgYHtyIG11bHRpTW9kZWxTdGFyZ2F6ZXJUZXgsIGVjaG89RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0KIyBTYXZpbmcgdGhlIHJlc3VsdHMgYXMgYSBsYXRleCB0YWJsZSwgYnV0IG5vdCBwcmludGluZyBpdCBvdXQgaW4gdGhlIE1hcmtkb3duIGRvY3VtZW50DQppbnZpc2libGUoc3RhcmdhemVyKGZpbmFsTW9kZWwsIHR5cGUgPSAnbGF0ZXgnLCBwLmF1dG8gPSBGQUxTRSwgb3V0PSIuLi9EYXRhL211bHRpLnRleCIsIA0KICAgICAgICAgICAgICAgICAgICBzaW5nbGUucm93ID0gVFJVRSwgaGVhZGVyID0gRkFMU0UpKQ0KYGBgDQoNCmBgYHtyIG11bHRpTW9kZWxTdGFyZ2F6ZXIsIHJlc3VsdHM9J2FzaXMnfQ0KIyB0YWJ1bGF0aW5nIHRoZSBtb2RlbCByZXN1bHRzIGFzIGFuIEhUTUwgdGFibGUsIHdoaWNoIHdlIHByaW50IGJlbG93DQpzdGFyZ2F6ZXIoZmluYWxNb2RlbCwgdHlwZSA9ICdodG1sJywgcC5hdXRvID0gRkFMU0UsIG91dD0iLi4vRGF0YS9tdWx0aS5odG1sIiwgc2luZ2xlLnJvdyA9IEZBTFNFKQ0KYGBgDQoNCiMjIyBNb2RlbCdzIFBlcmZvcm1hbmNlDQoNCmBgYHtyIG11bHRpTW9kZWxQZXJmfQ0KIyBleGFtaW5pbmcgaG93IHdlbGwgdGhlIG1vZGVsIHBlcmZvcm1lZCBvbiBvdXIgZW50aXJlIGRhdGFzZXQNCiMgUmVjYWxsIHRoYXQgd2UgYXJlIGZpdHRpbmcgYW4gZXhwbGFuYXRvcnkgbW9kZWwsIGFuZCBub3QgYSBwcmVkaWN0aXZlIG1vZGVsDQpwcmVkaWN0ZWRDbGFzcyA9IHByZWRpY3QoZmluYWxNb2RlbCwgZGYpDQpzYXZlUkRTKGZpbmFsTW9kZWwsICcuLi9EYXRhL2ZpbmFsTW9kZWwucmRzJykgIyBzYXZpbmcgdGhlIGRhdGENCg0KIyBDb21wdXRpbmcgdGhlIENvbmZ1c2lvbiBNZXRyaWNzIGFuZCBCeSBDbGFzcyBNZXRyaWNzDQpjb25mTWF0cml4ID0gY29uZnVzaW9uTWF0cml4KHByZWRpY3RlZENsYXNzLCBkZiRjbHVzdFJlTGV2ZWxlZCkNCnNhdmVSRFMoY29uZk1hdHJpeCwgJy4uL0RhdGEvY29uZk1hdHJpeC5yZHMnKSAjIHNhdmluZyB0aGUgZGF0YQ0KDQojIFByaW50aW5nIHRoZSByZXN1bHRpbmcgdGFibGVzIG5pY2VseQ0KcGFuZGVyKGNvbmZNYXRyaXgkdGFibGUpDQpwYW5kZXIoY29uZk1hdHJpeCRieUNsYXNzKQ0KcGFuZGVyKGNvbmZNYXRyaXgkb3ZlcmFsbCkNCmBgYA0KDQojIyMgVmlzdWFsaXppbmcgdGhlIE1vZGVs4oCZcyBQcmVkaWN0aW9ucw0KDQpgYGB7ciB2aXpNdWx0aU1vZGVsfQ0KcHJlZGljdGVkUHJvYnMgPSBmaXR0ZWQoZmluYWxNb2RlbCkgIyBjb21wdXRpbmcgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMgZm9yIGVhY2ggb2YgdGhlIGNsdXN0ZXIgb3V0Y29tZSBsZXZlbHMNCm1hcFJlc3VsdHMgPSBjYmluZChtdWx0aUNsYXNzREYsIHByZWRpY3RlZFByb2JzKSAjIGNvbCBiaW5kaW5nIHByZWRQcm9icyBmb3IgRWFjaCBDbHVzdGVyIHdpdGggbXVsdGlDbGFzc0RGDQoNCiMgRmluZGluZyBpbmRpY2VzIHRvIHN1YnNldCB0aGUgZGF0YQ0KbnVtYmVyT2ZDbHVzdGVycyA9IHVuaXF1ZShtYXBSZXN1bHRzJGNsdXN0ZXJfZ3JvdXApICU+JSBhcy5jaGFyYWN0ZXIoKSAlPiUgbGVuZ3RoKCkgDQpzdGFydENvbCA9IG5jb2wobWFwUmVzdWx0cykgLSBudW1iZXJPZkNsdXN0ZXJzICsgMQ0KZW5kQ29sID0gbmNvbChtYXBSZXN1bHRzKQ0KDQojIEZpbmRpbmcgd2hldGhlciB0aGUgcHJlZGljdGVkIGFuZCBhY3R1YWwgY2x1c3RlcnMgbWF0Y2hlZCBmb3IgZWFjaCBjb3VudHkNCm1hcFJlc3VsdHMkTGFyZ2VzdFByb2JDbHVzdGVyID0gY29sbmFtZXMobWFwUmVzdWx0c1ssIHN0YXJ0Q29sOmVuZENvbF0pW2FwcGx5KG1hcFJlc3VsdHNbLCBzdGFydENvbDplbmRDb2xdLCAxLCB3aGljaC5tYXgpXSANCm1hcFJlc3VsdHMkbWF0Y2ggPSBpZmVsc2UobWFwUmVzdWx0cyRjbHVzdGVyX2dyb3VwID09IG1hcFJlc3VsdHMkTGFyZ2VzdFByb2JDbHVzdGVyLCAnWWVzJywgJ05vJykgJT4lIGFzLmZhY3RvcigpDQoNCiMgUmV0cmlldmluZyB0aGUgVS5TLiBjb3VudHkgY29tcG9zaXRlIG1hcCBhcyBhIHNpbXBsZWZlYXR1cmUgKHNpbmNlIGl0IGhhcyBiZWVuIG92ZXJ3cml0dGVuKQ0KY3R5X3NmID0gY291bnRpZXNfc2YoImFlcWQiKSAlPiUgZmlsdGVyKCFzdGF0ZSAlaW4lIGMoJ0FsYXNrYScsICdIYXdhaWknKSkgIyBmcm9tIGFsYmVyc3VzYQ0KY3R5X3NmICU8PiUgZ2VvX2pvaW4obWFwUmVzdWx0cywgYnlfc3A9ICdmaXBzJywgYnlfZGY9ICdmaXBzJykNCg0KIyBDcmVhdGluZyBhIHN0YXRpYyB2aXN1YWwgZm9yIHVzZSBpbiB0aGUgcGFwZXINCnRpZmYoZmlsZW5hbWUgPSAnLi4vRmlndXJlcy9jbHVzdGVyTWF0Y2hNYXAudGlmZicsIHdpZHRoID0gMTM2NiwgaGVpZ2h0ID03NjgsIHBvaW50c2l6ZSA9IDE2KQ0KdG1fc2hhcGUoY3R5X3NmKSArIHRtX3BvbHlnb25zKCdtYXRjaCcsIHRpdGxlID0gJ0NsdXN0ZXIgTWF0Y2gnLCBzdHlsZSA9ICdjb250JywgcGFsZXR0ZSA9ICJkaXYiKSArDQogIHRtX2xheW91dChhZXMucGFsZXR0ZSA9IGxpc3QoZGl2ID0gbGlzdCgiWWVzIiA9ICIjQ0FCMkQ2IiwgIk5vIiA9ICIjNkEzRDlBIikpKSArDQogIHRtX2NyZWRpdHMocGFzdGUwKCdCYXNlZCBvbiBEYXRhIGZyb20gTWFyY2ggMDEsIDIwMjAgLSAnLCBlbmREYXRlUHJpbnRWKSwgcG9zaXRpb249YygicmlnaHQiLCAiYm90dG9tIikpDQppbnZpc2libGUoIGRldi5vZmYoKSApICMgdG8gc3VwcHJlc3MgdGhlIHVud2FudGVkIG91dHB1dCBmcm9tIGRldi5vZmYNCg0KDQojIENyZWF0aW5nIGFuIGludGVyYWN0aXZlIHZpc3VhbCBVc2luZyB0aGUgTGVhZmxldCBQYWNrYWdlDQojIyMjIENyZWF0aW5nIGEgbG9uZ2xhdCBwcm9qZWN0aW9uIChyZXF1aXJlZCBieSBsZWFmbGV0KQ0KbGVhZmxldF9zZiA9IGNvdW50aWVzX3NmKCJsb25nbGF0IikgJT4lIGZpbHRlcighc3RhdGUgJWluJSBjKCdBbGFza2EnLCAnSGF3YWlpJykpICMgZnJvbSBhbGJlcnN1YQ0KbGVhZmxldF9zZiAlPD4lIGdlb19qb2luKG1hcFJlc3VsdHMsIGJ5X3NwPSAnZmlwcycsIGJ5X2RmPSAnZmlwcycpDQoNCiMjIyMgU2V0dGluZyB0aGUgQ29sb3IgU2NoZW1lDQpsZWFmbGV0UGFsID0gIGNvbG9yRmFjdG9yKHBhbGV0dGUgPSBjKCIjQ0FCMkQ2IiwgIiM2QTNEOUEiKSwgbGV2ZWxzID0gYygnWWVzJywgJ05vJyksIG5hLmNvbG9yID0gIndoaXRlIikNCg0KIyMjIyBUaGUgdmlzdWFsDQpsZWFmbGV0KGhlaWdodD01MDApICU+JSAjIGluaXRpYWxpemluZyB0aGUgbGVhZmxldCBtYXANCiAgc2V0VmlldyhsbmcgPSAtOTYsIGxhdCA9IDM3LjgsIHpvb20gPSAzLjgpICU+JSAjIHNldHRpbmcgdGhlIHZpZXcgb24gQ29udGluZW50YWwgVVMNCiAgYWRkVGlsZXMoKSAlPiUgIyBhZGRpbmcgdGhlIGRlZmF1bHQgdGlsZXMNCiAgYWRkUG9seWdvbnMoZGF0YSA9IGxlYWZsZXRfc2YsIHN0cm9rZSA9IEZBTFNFLCBmaWxsQ29sb3IgPSB+bGVhZmxldFBhbChsZWFmbGV0X3NmJG1hdGNoKSwgIyBhZGRpbmcgdGhlIGRhdGENCiAgICAgICAgICAgICAgd2VpZ2h0ID0gMiwgb3BhY2l0eSA9IDEsIGNvbG9yID0gIndoaXRlIiwgZGFzaEFycmF5ID0gIjMiLCBmaWxsT3BhY2l0eSA9IDAuNywgIyBhZGRpbmcgY29sb3Igc3BlY3MNCiAgICAgICAgICAgICAgcG9wdXAgPSBwYXN0ZSgiQ291bnR5OiIsIGxlYWZsZXRfc2YkbmFtZSwgJzxicj4nLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQ2x1c3RlciAjOiIsIGxlYWZsZXRfc2YkY2x1c3Rlcl9ncm91cCwgJzxicj4nLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDbHVzdGVyIFByZWRpY3RlZDoiLCBsZWFmbGV0X3NmJExhcmdlc3RQcm9iQ2x1c3RlciwgJzxicj4nLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDbHVzdGVyIE1hdGNoOiIsIGxlYWZsZXRfc2YkbWF0Y2gsICc8YnI+JykpICU+JSAjcG9wLXVwIE1lbnUNCiAgYWRkTGVnZW5kKHBvc2l0aW9uID0gImJvdHRvbWxlZnQiLCBwYWwgPSBsZWFmbGV0UGFsLCB2YWx1ZXMgPSAgbGVhZmxldF9zZiRtYXRjaCwgDQogICAgICAgICAgICB0aXRsZSA9ICJDbHVzdGVyIE1hdGNoIiwgb3BhY2l0eSA9IDEpICMgbGVnZW5kIGZvcm1hdHRpbmcNCmBgYA0K